Rechercher sur arkzoyd.com

26 mai 2009

VARCHAR2(4000 char) ne permet pas de stocker 4000 caractères

Si vous avez une base Oracle qui utilise des formats de caractères sur plusieurs octets comme AL32UTF8, vous connaissez la différence entre BYTE et CHAR. Par défaut le paramètre nls_length_semantics est positionné à la valeur BYTE, ce qui signifie que lorsque vous créez une colonne de type VARCHAR2(X), elle peut contenir au maximum X octets et non pas X caractères. Voici une illustration de ce point avec le schéma SCOTT sur une base paramétrée en AL32UTF8:
create table T(col1 varchar2(2));

insert into T values ('éé');
*
ERROR at line 1:
ORA-12899: value too large for column "SCOTT"."T"."COL1" (actual: 4, maximum: 2)
Pour éviter ce type de problèmes, vous pouvez positionner le paramètre nls_length_semantics à CHAR ou au choix préciser VARCHAR2(X CHAR) :
alter session set nls_length_semantics=CHAR;

drop table T purge;

create table T(col1 varchar2(2));

insert into T values ('éé');

1 row created.

select * from T

COL1
----
éé
Vous pouvez vérifier que la table contient bien un VARCHAR2(2 CHAR) simplement avec la commande desc:
SQL> desc T

Name Null? Type
---- ----- ----------------
COL1 VARCHAR2(2 CHAR)
Vous pouvez vérifier aussi utiliser le paramètre SIZE_BYTE_KEYWORD avec dbms_metadata:
set lines 32000
set serveroutput on buffer 20000
declare
handle NUMBER;
tr_handle NUMBER;
ret CLOB;
begin

-- Open a handle for tables
handle := DBMS_METADATA.OPEN('TABLE','11.1.0.7');

-- Get the SCOTT tables
DBMS_METADATA.SET_FILTER(handle, 'NAME','T');
DBMS_METADATA.SET_FILTER(handle, 'SCHEMA',user);

-- Display the command with the
-- DDL syntax
tr_handle := dbms_metadata.ADD_TRANSFORM(
handle, 'DDL');
dbms_metadata.SET_TRANSFORM_PARAM(tr_handle,
'SIZE_BYTE_KEYWORD', TRUE);
dbms_metadata.SET_TRANSFORM_PARAM(tr_handle,
'SEGMENT_ATTRIBUTES', FALSE);
dbms_metadata.SET_TRANSFORM_PARAM(tr_handle,
'STORAGE', FALSE);

dbms_output.enable;
ret:='X';
while (ret is not null) loop
-- Fetch the commands in a CLOB
ret := dbms_metadata.FETCH_CLOB(handle);

-- Print the SQL text
dbms_output.put_line(ret);
end loop;

-- Close the handle
dbms_metadata.CLOSE(handle);
end;
/

CREATE TABLE "SCOTT"."T"
( "COL1" VARCHAR2(2 CHAR)
)
Mais voilà, la taille maximum d'un VARCHAR2 est bien 4000 octets et pas 4000 caractères:
drop table T purge;

create table T(col1 varchar2(4000 char));

insert into T values (rpad('é',3000,'é')||'é');
*
ERROR at line 1:
ORA-01489: result of string concatenation is too long

21 mai 2009

Prouvez-le !

J'ai lu un blog aujourd'hui qui parle des lignes chainées avec Oracle et qui dit que cela arrive lorsqu'on utilise des LOB. Je ne référencerai pas cet article parce que son auteur ne le mérite pas mais le fait est que l'utilisation d'un LOB, même d'une taille supérieure à la taille d'un bloc, ne suffit pas à générer des lignes chainées avec Oracle.

Bien sur, vous pouvez lire la documentation à propos des listes chainées et vous verrez qu'il n'y est pas fait mention des LOB mais seulement des LONG et LONG RAW. Cela dit, ce n'est pas parce que la documentation ne dit pas quelque chose que c'est faux ! Pas vrai ?

Il y a plusieurs façons de comprendre quelque chose et Oracle étant, malgré tout, loin de l'astrophysique des approches simples fonctionnent très bien. Vous pouvez utiliser une approche inductive (strace, 10046, ou dans ce cas, faire un dump des blocs de données avec la commande alter system dump datafile ...). Vous pouvez aussi suivre un raisonnement hypothético-déductif comme ci-dessous. Enfin, vous pouvez, comme le MI-6, évaluer chaque information selon sa probabilité (likelyhood) et la fiabilité de la source (documentation 98%, J. Lewis 99.9%). Vous pourriez donc juste me croire. Cela suppose toutefois que vous soyez capable d'appréhender la probabilité de cette information ou que je sois une source fiable...

Au final, une approche déductive - vive les mathématiques ! - est une bonne façon d'aborder de nombreux aspects d'Oracle. Vous n'aurez pour cela besoin de rien d'autre que vous-même et de l'envie de prouver vos hypothèses. Alors pouvons que si vous stockez une données dans un LOB, la ligne correspondante n'est pas chainée au sens Oracle du terme. Nous allons commencer, pour fixer les idées, avec une table qui contient un LONG:
connect system/manager

create table T
(id number,
text long);
/
J'ai écrit un programme Perl qui utilise DBD::Oracle pour insérer une données de 8000 caractères dans la colonne LONG ou CLOB:
#!/bin/perl
use strict;
use DBI;

my $dbh = DBI->connect('DBI:Oracle:ORCL',
'system', 'manager',
{ AutoCommit => 1})
or die "Cannot connect : " . DBI->errstr;

$dbh->{LongReadLen} = 10000;
my $mylong = 'K' x ( 8000 );

my $sth = $dbh->prepare(
'insert into t values (1,?)')
or die "Cannot create statement: "
. $dbh->errstr;

$sth->execute( $mylong )
or die "Cannot execute statement: "
. $sth->errstr;

$sth = $dbh->prepare(
'select id, text from t')
or die "Cannot create statement: "
. $dbh->errstr;

$sth->execute()
or die "Cannot execute statement: "
. $sth->errstr;

my @data;
while (@data = $sth->fetchrow_array()) {
my $id = $data[0];
my $text = $data[1];
print "\t Id: $id \n";
print "\tText: $text \n";
}

$sth->finish;
$dbh->disconnect;
Je lance le programme et regarde le nombre de lignes chainées pour ma table T:
$ perl test.pl

sqlplus system/manager

@?/rdbms/admin/utlchain.sql
truncate table chained_rows;

analyze table t list chained rows;

col table_name format a5
select table_name, head_rowid
from chained_rows;

TABLE HEAD_ROWID
----- ------------------
T AAASDDAABAAAWz6AAA
Maintenant, refaisons le test avec un CLOB:
sqlplus system/manager

drop table T purge;

create table T
(id number,
text clob);

exit;

perl test.pl

sqlplus system/manager

truncate table chained_rows;

analyze table t list chained rows;

col table_name format a5
select table_name, head_rowid
from chained_rows;

no rows selected
Et voilà, malgré l'insertion d'un champs CLOB de 8000 caractères, la ligne n'apparait pas dans la liste des lignes chainées et migrées de la table. La raison en est que le champ est stocké au moins en partie dans un autre segment de la base de données, ce qui n'est pas le cas pour un LONG:
select segment_name
from user_lobs
where table_name='T';

SEGMENT_NAME
-------------------------
SYS_LOB0000073926C00002$$

18 mai 2009

Transformer vos scripts DDL avec DBMS_METADATA

Non seulement DBMS_METADATA permet de récupérer le DDL d'un schéma mais il permet aussi de le transformer. Dans l'exemple qui suit, vous trouverez le code qui extrait le DDL des tables du schéma SCOTT, supprime la clause de stockage et transforme les noms des schémas et des tablespaces associés:
set lines 32000
set serveroutput on buffer 20000
declare
handle NUMBER;
tr_handle NUMBER;
ret CLOB;
begin
-- Open a handle for tables
handle := DBMS_METADATA.OPEN('TABLE','11.1.0.7');

-- Get the SCOTT tables
DBMS_METADATA.SET_FILTER(handle, 'SCHEMA',
'SCOTT');

-- Transform the tables to be created
-- in the DEMO schema and to be stored
-- in the DEMO tablespace
tr_handle := DBMS_METADATA.ADD_TRANSFORM(
handle, 'MODIFY');
dbms_metadata.SET_REMAP_PARAM(tr_handle,
'REMAP_SCHEMA', 'SCOTT',
'DEMO');
dbms_metadata.SET_REMAP_PARAM(tr_handle,
'REMAP_TABLESPACE', 'USERS',
'DEMO');

-- Display the command with the
-- DDL syntax
tr_handle := dbms_metadata.ADD_TRANSFORM(
handle, 'DDL');
dbms_metadata.SET_TRANSFORM_PARAM(tr_handle,
'SQLTERMINATOR', TRUE);
dbms_metadata.SET_TRANSFORM_PARAM(tr_handle,
'SEGMENT_ATTRIBUTES', TRUE);
dbms_metadata.SET_TRANSFORM_PARAM(tr_handle,
'STORAGE', FALSE);
dbms_metadata.SET_TRANSFORM_PARAM(tr_handle,
'TABLESPACE', TRUE);

dbms_output.enable;
ret:='1';
while (ret is not null) loop
-- Fetch the commands in a CLOB
ret := dbms_metadata.FETCH_CLOB(handle);

-- Print the SQL text
dbms_output.put_line(ret);
end loop;

-- Close the handle
dbms_metadata.CLOSE(handle);
end;
/
set lines 120
Le résultat ressemble à ceci:
  CREATE TABLE "DEMO"."BONUS"
( "ENAME" VARCHAR2(10),
"JOB" VARCHAR2(9),
"SAL" NUMBER,
"COMM" NUMBER
) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING
TABLESPACE "DEMO" ;


CREATE TABLE "DEMO"."DEPT"
( "DEPTNO" NUMBER(2,0),
"DNAME" VARCHAR2(14),
"LOC" VARCHAR2(13),
CONSTRAINT "PK_DEPT" PRIMARY KEY ("DEPTNO")
USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255
TABLESPACE "DEMO" ENABLE
) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING
TABLESPACE "DEMO" ;


CREATE TABLE "DEMO"."EMP"
( "EMPNO" NUMBER(4,0),
"ENAME" VARCHAR2(10),
"JOB" VARCHAR2(9),
"MGR" NUMBER(4,0),
"HIREDATE" DATE,
"SAL" NUMBER(7,2),
"COMM" NUMBER(7,2),
"DEPTNO" NUMBER(2,0),
CONSTRAINT "PK_EMP" PRIMARY KEY ("EMPNO")
USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255
TABLESPACE "DEMO" ENABLE,
CONSTRAINT "FK_DEPTNO" FOREIGN KEY ("DEPTNO")
REFERENCES "DEMO"."DEPT" ("DEPTNO") ENABLE
) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING
TABLESPACE "DEMO" ;


CREATE TABLE "DEMO"."SALGRADE"
( "GRADE" NUMBER,
"LOSAL" NUMBER,
"HISAL" NUMBER
) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING
TABLESPACE "DEMO" ;
Si vous êtes curieux, regardez la liste des paramètres de SET_TRANSFORM_PARAM qui contrôlent les sections affichées dans le DDL. Les paramètres de SET_REMAP_PARAM permettent quant à eux de renommer certaines propriétés comme le nom, le schéma ou le tablespace d'un objet.

09 mai 2009

OracleVM et Dell E6500

Cette semaine, et après plus de 2 ans à travailler avec des sociétés aux US, j'ai recommencé à travailler en France. C'est promis, vous aurez plus de détails dans un prochain post...

Parmi les réjouissances que je peux partager avec vous : 2 jours de workshop Exadata/Database Machine, la promesse de migrer sur Beehive 1.5.1 dans les semaines à venir, un iPhone et un nouveau laptop, un Dell E6500. Je me suis empressé d'installer Jaunty Jackalope, Firefox 3.5 Beta et tous mes Addons pour redevenir fonctionnel rapidement... mais c'est déjà une autre histoire; cela dit, il va me falloir 3 mois pour me remettre sur un clavier français.

J'ai profité de l'opportunité et d'un disque de 300GB pour installer OracleVM. Plutôt facile me direz vous :
  • Si vous avez partitionné votre disque n'importe comment, comme moi, vous pouvez démarrer une distribution Linux, avec le CD-ROM Ubuntu par exemple, et utiliser GParted (System > Administration > Partition Editor) pour re-partitionner votre disque. Si vous avez un espace de swap, il est possible que Ubuntu l'utilise, même si vous démarrez depuis un CD-ROM. Dans ce cas, désactiver (swapoff), l'utilisation de la swap pour re-partitionner le disque sans tout effacer.

  • Il semble qu'OracleVM ne supporte pas "Intel(R) Rapid Recovery Technology" pour la configuration SATA; ou peut-être le supporte-t-il et je n'ai pas sur le configurer; toujours est-il que j'ai changé la configuration du BIOS de mon laptop et après avoir sélectionné : "System Configuration > SATA Operation >
    (o) ATA - SATA is configured for ATA", j'arrive à faire fonctionner Ubuntu et OracleVM.

  • Pour Ubuntu, j'ai configuré GRUB dans le Master Boot Record (MBR). Pour OracleVM, j'ai installé une seconde configuration GRUB mais cette fois-ci dans le premier secteur de la partition d'OracleVM. Pour référencer la seconde configuration depuis la première, j'ai ajouté la section suivante dans le fichier /boot/grub/menu.lst :
# This entry has been added to boot Oracle VM Grub
# Configuration from first sector of boot partition
# of /dev/sda4
title -> Oracle Virtual Server 2.1.2
rootnoverify (hd0,3)
makeactive
chainloader +1
J'ai déployé 2 templates OEL JeOS 5.3, et j'attaquerai l'installation de mon nouveau RAC la semaine prochaine...

Le E6500 parait un bon compromis pour une utilisation professionnelle. Il permet également de démarrer sur une clé USB et j'installerai sans doute OracleVM sur une clé USB un de ces 4 matins. Dommage qu'il ne soit pas 64 bits; j'aurais bien aimé créer une partition Solaris x86_64 aussi; les versions Oracle 10.2 et 11.1 ne sont pas disponibles sur Solaris x86 en 32 bits. Pour une utilisation privée, préferez peut-être un laptop avec une interface HDMI.