Canalblog
Editer l'article Suivre ce blog Administration + Créer mon blog
Publicité
Blog d'un DBA sur le SGBD Oracle et SQL
15 septembre 2017

Les informations affichées par Oracle ne sont pas toujours fiables - The information displayed by Oracle is not always reliable



Introduction
Un problème agaçant avec Oracle est que les informations recherchées sont parfois difficiles à trouver voir même sont incomplètes. Soit ces infos sont dispersées dans plusieurs vues sans point d'entrée unique, soit des paramètres de niveau base sont écrasés par des paramètres de niveau local soit les infos ne sont pas où le DBA junior pensent qu'elles devraient être.

Oracle est devenu au fil des versions de plus en plus complexe et des règles d'anciennes versions sont obsolètes pour les nouvelles versions, comme par exemple que le DESC permet de voir les colonnes NOT NULL.


 
Points d'attention
N/A.
 



Base de tests
N'importe quelle base Oracle.
 



Exemples
============================================================================================
Une contrainte UNIQUE peut se poser sur la colonne ou sur l'index

============================================================================================

Dans une table il est possible de définir une contrainte UNIQUE au niveau de la colonne pour empêcher la saisie de deux valeurs identiques (à l'exception du NULL). Cette contrainte est visible, non pas en faisant un DESC comme pour les contraintes NOT NULL (du moins certaines :-) ), mais en regardant dans les vues XXX_CONSTRAINTS.
          SQL> create table TEST_UNIQ (ID NUMBER(2) UNIQUE)

          Table created.

          SQL> desc TEST_UNIQ
          Name           Null?    Type
          -----------------------------------------
          ID                            NUMBER(2)

          SQL> select CONSTRAINT_NAME, CONSTRAINT_TYPE, STATUS from dba_constraints where table_name = 'TEST_UNIQ' AND OWNER = 'HR';
          CONSTRAINT_NAME      C      STATUS
          -------------------- - ------------------------------
          SYS_C0011856               U      ENABLED

          SQL> insert into TEST_UNIQ values (1);
          1 row created.

          SQL> /
          insert into TEST_UNIQ values (1)
          *
          ERROR at line 1:
          ORA-00001: unique constraint (HR.SYS_C0011856) violated

On crée maintenant une deuxième table, sans contrainte unique et, une fois celle-ci créée, on ajoute un index UNIQUE. Dans les vues XXX_CONSTRAINTS cette table n'a toujours pas de contrainte UNIQUE, néanmoins elle réagit de la même façon que la table précédente car l'index sur cette colonne interdit la saisie de deux valeurs identiques. Vous noterez que le message d'erreur est le même dans les deux cas : "ORA-00001: unique constraint () violated", ce qui montre que la vérification de l'unicité d'une valeur se fait en parcourant l'index.
          SQL> create table TEST_UNIQ_IDX (ID NUMBER(2));
          Table created.

          SQL> create UNIQUE index IDX_UNIQ on TEST_UNIQ_IDX(ID);
          Index created.

          SQL> select CONSTRAINT_NAME, CONSTRAINT_TYPE, SEARCH_CONDITION, STATUS from dba_constraints where table_name = 'TEST_UNIQ_IDX' and OWNER = 'HR';
          no rows selected

          SQL> select INDEX_NAME, INDEX_TYPE, UNIQUENESS from user_indexes where TABLE_NAME = 'TEST_UNIQ_IDX';
          INDEX_NAME      INDEX_TYPE         UNIQUENES
          ---------- --------------------------- -----------------------------------
          IDX_UNIQ            NORMAL               UNIQUE

          SQL> insert into TEST_UNIQ_IDX values (1);
          1 row created.

          SQL> /
          insert into TEST_UNIQ_IDX values (1)
          *
          ERROR at line 1:
          ORA-00001: unique constraint (HR.IDX_UNIQ) violated

Oracle permet de définir des contraintes d'intégrité UNIQUE de deux façons : au niveau de la colonne ou au niveau de l'index. Et, in fine, le problème pour le DBA junior ou le développeur souhaitant lister les contraintes d'intégrité, est qu'il ne peut plus se limiter à un SELECT dans les vues XXX_CONSTRAINTS mais doit obligatoirement faire un UNION avec les vues XXX_INDEXES pour afficher les contraintes UNIQUES définies au niveau des indexes.
          SQL> select CONSTRAINT_NAME, CONSTRAINT_TYPE, STATUS from user_constraints where table_name = 'TEST_UNIQ_IDX'
          UNION
          select INDEX_NAME, INDEX_TYPE, UNIQUENESS from user_indexes where TABLE_NAME = 'TEST_UNIQ_IDX';
          CONSTRAINT_NAME      CONSTRAINT_TYPE         STATUS
          -------------------- --------------------------- --------- ---------------------------------
          IDX_UNIQ                        NORMAL                         UNIQUE

OK, OK, on est d'accord pour dire que stricto sensu créer un index UNIQUE ne crée pas une contrainte d'intégrité sur la colonne puisque celle-ci n'est pas dans XXX_CONSTRAINTS. Mais, comme le résultat est identique, à savoir impossibilité de créer deux fois la même valeur, on peut considèrer que c'est l'équivalent d'une contrainte d'intégrité.

============================================================================================
Taille d'un bloc de données : paramètre DB_BLOCK_SIZE ou colonne du tablespace

============================================================================================

Le paramètre DB_BLOCK_SIZE est le paramètre donnant la taille par défaut du bloc de données. Depuis plusieurs versions d'Oracle ce paramètre peut être écrasé au niveau du tablespace, exceptions faites des tablespaces SYSTEM et SYSAUX qui eux utilisent DB_BLOCK_SIZE.
          SQL> show parameter block_size
          NAME               TYPE     VALUE
          --------------------------------------------------
          db_block_size    integer     8192

          SQL> select TABLESPACE_NAME, BLOCK_SIZE from dba_tablespaces order by 1;
          TABLESPACE_NAME                    BLOCK_SIZE
          ------------------------------ ---------- -------------------------------
          APEX_1991375173370654            8192
          APEX_1993195660370985            8192
          SYSAUX                             8192
          SYSTEM                             8192
          TEMP                             8192
          UNDOTBS1                         8192
          USERS                             8192
          7 rows selected.

Nous allons créer un tablespace avec une taille de bloc différent du paramètre par défaut. Avant cela, il faut une zone mémoire dimensionnée pour accepter les blocs de taille non standard de ce tablespace sinon le CREATE TABLESPACE échoue.
          SQL> create tablespace "TEST_4K" DATAFILE '/u01/app/oracle/oradata/orcl12c/orcl/users_4K.dbf' SIZE 10M blocksize 4k EXTENT MANAGEMENT LOCAL SEGMENT SPACE MANAGEMENT AUTO
               *
          ERROR at line 1:
          ORA-29339: tablespace block size 4096 does not match configured block sizes

          SQL> show parameter _k_cache
          NAME                              TYPE             VALUE
          ------------------------------------ ----------- ------------------------------
          db_2k_cache_size             big integer         0
          db_4k_cache_size             big integer         0
          db_8k_cache_size             big integer         0
          db_16k_cache_size             big integer       0
          db_32k_cache_size             big integer       0

Cette opération de configuration de la SGA se fait dans le CDB$ROOT si on est en Oracle 12c architecture multi-tenant.
          SQL> alter system set db_4k_cache_size = 16m;
          alter system set db_4k_cache_size = 16m
          *
          ERROR at line 1:
          ORA-65040: operation not allowed from within a pluggable database

          SQL> connect SYS@orcl12c as sysdba
          Enter password:
          Connected.

          SQL> show con_name
          CON_NAME
          ------------------------------
          CDB$ROOT

          SQL> alter system set db_4k_cache_size = 16m;
          System altered.

          SQL> show parameter _k_cache
          NAME                               TYPE              VALUE
          ------------------------------------ ----------- ------------------------------
          db_2k_cache_size             big integer          0
          db_4k_cache_size             big integer     16M
          db_8k_cache_size             big integer          0
          db_16k_cache_size             big integer        0
          db_32k_cache_size             big integer        0

On se reconnecte à la PDB et on crée le tablespace.
          SQL> connect HR/HR@orcl
          Connected.

          SQL> create tablespace "TEST_4K" DATAFILE '/u01/app/oracle/oradata/orcl12c/orcl/users_4K.dbf' SIZE 10M blocksize 4k EXTENT MANAGEMENT LOCAL SEGMENT SPACE MANAGEMENT AUTO;
          Tablespace created.

On voit bien que dans DBA_TABLESPACES la taille du bloc du nouveau tablespace a une taille différente de celui des autres tablespaces.
          SQL> select TABLESPACE_NAME, BLOCK_SIZE from dba_tablespaces order by 1;
          TABLESPACE_NAME            BLOCK_SIZE
          ----------------------------------------------------- ----------
          APEX_1991375173370654     8192
          APEX_1993195660370985     8192
          SYSAUX                     8192
          SYSTEM                     8192
          TEMP                     8192
          TEST_4K                  4096
          UNDOTBS1                 8192
          USERS                     8192
          8 rows selected.

Qu'est-ce que cela implique pour le DBA? Hé bien, que si vous utilisez un script calculant la taille de table ou d'index en utilisant le paramètre DB_BLOCK_SIZE, ce script sera faux pour les objets d'un tablespace créé avec une taille de bloc différente de DB_BLOCK_SIZE. On peut même dire que dans les dernières versions d'Oracle, le paramètre DB_BLOCK_SIZE est utilisé lors de la création de la base mais qu'ensuite c'est le champ BLOCK_SIZE de la table DBA_TABLESPACES qui fait foi.

Testons cela avec une table de 999 éléments.

          SQL> insert into TABLE_4K SELECT 'TEST' FROM dual connect by level < 1000;
          999 rows created.

          SQL> commit;
          Commit complete.

La taille de la table (327 680 octets) correspond bien à 80 blocs de 4 096 octets et non pas à 80 blocs de taille DB_BLOCK_SIZE pour notre base.
          SQL> select BYTES, BLOCKS from dba_segments where SEGMENT_NAME = 'TABLE_4K';
          BYTES     BLOCKS
          ---------- ----------
          327680       80

Vérification de la taille avec les valeurs en dur.
          SQL> select 80 * 4096 from dual;
          80*4096
          ----------
          327680

Vérification avec les champs de la table et du tablespace.
          SQL> select (select blocks from dba_segments where SEGMENT_NAME = 'TABLE_4K') * (SELECT BLOCK_SIZE from dba_tablespaces where tablespace_name = 'TEST_4K') AS "TAILLE TABLE"  from dual;
          TAILLE TABLE
          ------------
          327680


============================================================================================
Contrainte d'intégrité NOT NULL invisible avec le DESC et active que pour les nouveaux enregistrements

============================================================================================

Une contrainte d'intégrité NOT NULL ne signifie pas que tous les enregistrements de la table aient une valeur NOT NULL pour cette colonne! Etonnant, non? Et, plus fort, un certain type de contrainte NOT NULL n'apparait pas avec DESC alors que beaucoup de personnes utilisent cette technique pour ne pas faire de SELECT dans les vues XXX_CONSTRAINTS.

Pour rappel, Oracle a introduit les contraintes d'intégrité avec sa version 7 en 1992, soit il y a 25 ans; depuis ce temps, celles-ci se sont enrichies de beaucoup de fonctionnalités, donc certaines assez déroutantes.

La table ZZ02 a deux colonnes, dont une seule est NOT NULL.
          SQL> desc ZZ02;     
          Name                     Null?              Type
          --------------------------------------- ---------------------------------
          TABLE_NAME        NOT NULL     VARCHAR2(128)
          TABLE_TYPE                              VARCHAR2(11)

          SQL> select * from zz02;
          no rows selected

          SQL> insert into ZZ02 values ('TEST01', 'TABLE');
          1 row created.

          SQL> insert into ZZ02 values ('TEST02', 'VIEW');
          1 row created.

          SQL> insert into ZZ02 values ('TEST03', NULL);
          1 row created.

          SQL> commit;
          Commit complete.

          SQL> select * from zz02 order by 1;
          TABLE_NAME           TABLE_TYPE
          ---------------------------------------- -----------
          TEST01                     TABLE
          TEST02                     VIEW
          TEST03

Il est désormais possible avec l'option NOVALIDATE de créer une contrainte d'intégrité active uniquement pour les nouveaux enregistrements. L'alter table passe sans problème alors qu'on a pourtant un enregistrement dans la table avec le champ TABLE_TYPE à NULL.
          SQL> alter table ZZ02 modify TABLE_TYPE NOT NULL NOVALIDATE
          Table altered.

Vous noterez que la nouvelle contrainte NOT NULL avec l'option NOVALIDATE n'apparait pas dans le DESC de la table! Et pourtant, au niveau du dictionnaire de données, les deux contraintes NOT NULL existent bel et bien et ne différent que par la colonne VALIDATED.
          SQL> desc ZZ02
          Name                               Null?                   Type
          -------------------------------- -------- -------------------------------------------------
          TABLE_NAME                  NOT NULL           VARCHAR2(128)
          TABLE_TYPE                                              VARCHAR2(11)

          SQL> SELECT CONSTRAINT_NAME, CONSTRAINT_TYPE, SEARCH_CONDITION,  STATUS,  VALIDATED, LAST_CHANGE FROM USER_CONSTRAINTS WHERE TABLE_NAME = 'ZZ02';
          CONSTRAINT_NAME     CONSTRAINT_TYPE        SEARCH_CONDITION                 STATUS          VALIDATED          LAST_CHANGE
          --------------- ---------------     ------------------------    -------     -------------   ------------ ------------------ ----------------------------- ------
          SYS_C0011766              C                                    "TABLE_NAME" IS NOT NULL    ENABLED       VALIDATED          08-SEP-17        
          SYS_C0011855              C                                    "TABLE_TYPE" IS NOT NULL     ENABLED       NOT VALIDATED  14-SEP-17       
 

          SQL> insert into ZZ02 values ('TEST_NOT_NULL', 'IOT');
          1 row created.

La contrainte d'intégrité NOT NULL NOVALIDATE est active pour les nouveaux enregistrements et le code erreur associé est le même que celui de la contrainte NOT NULL classique.
          SQL> insert into ZZ02 values (NULL, 'TABLE');
          insert into ZZ02 values (NULL, 'TABLE')
                                   *
          ERROR at line 1:
          ORA-01400: cannot insert NULL into ("HR"."ZZ02"."TABLE_NAME")

         
SQL> insert into ZZ02 values ('TEST_NOT_NULL', NULL)

          insert into ZZ02 values ('TEST_NOT_NULL', NULL)
                                                    *
          ERROR at line 1:
          ORA-01400: cannot insert NULL into ("HR"."ZZ02"."TABLE_TYPE")

Pourtant Oracle détecte bien que certains enregistrements sont NULL même si la colonne est maintenant considérée comme NOT NULL. Par exemple, avec la fonction COUNT, seuls deux enregistrements sur quatre sont traités pour le champ TABLE_TYPE.
          SQL> select count(*) from zz02;
          COUNT(*)
          ----------
               4

          SQL> select count(table_type) from zz02;
          COUNT(TABLE_TYPE)
          -----------------
                  2

Je vous conseille donc dorénavant de ne plus vous fier au DESC d'une table pour identifier ses colonnes NOT NULL.


Publicité
Publicité
Commentaires
Blog d'un DBA sur le SGBD Oracle et SQL
Publicité
Archives
Blog d'un DBA sur le SGBD Oracle et SQL
  • Blog d'un administrateur de bases de données Oracle sur le SGBD Oracle et sur les langages SQL et PL/SQL. Mon objectif est de vous faire découvrir des subtilités de ce logiciel, des astuces, voir même des surprises :-)
  • Accueil du blog
  • Créer un blog avec CanalBlog
Visiteurs
Depuis la création 340 506
Publicité