12c: les niveaux de compression changent de nom

En 12c les niveaux de compression changent de noms, en présisant le stockage en ligne ou colonne

  • en ligne (ROW STORE): les colonnes d’une même ligne sont ensembles, les lignes sont stockées les unes à la suite des autres dans des blocs
  • ou en colonne (COLUMN STORE): les valeurs des colonnes de plusieurs lignes sont regroupées en vecteurs, Les vecteurs sont stockés dans le Compression Unit
    Si vous avez déjà utilisé des Host Array pour insérer en bulk, c’est un peu la même idée. On a une meilleur compression lorsqu’on retrouve les mêmes valeurs d’une ligne à l’autre.

Sans option Advanced compression:

NOCOMPRESS

Pas de compression

ROW STORE COMPRESS BASIC

Basic Table Compression (aka ‘COMPRESS’ ou ‘ROW STORE COMPRESS’)
Lors des insert en direct-path, au niveau de chaque bloc, les valeurs dupliquées ne sont stockées qu’une fois (en utilisant des pointeurs – utile seulement pour des tailles supérieures à quelques octets)
Lors de DML conventionnel, les lignes sont décompressées, donc utile en chargement de masse seulement.

Avec option Advanced Compression

ROW STORE COMPRESS ADVANCED

Advanced Table Compression (aka ‘COMPRESS FOR OLTP’)
Même chose que BASIC, mais le DML conventionnel permet de compresser aussi.

sous Exadata et ZFS, avec stockage colonne

COLUMN STORE COMPRESS FOR QUERY LOW/HIGH

Warehouse compression (aka Hybrid Columnar Compression QUERY LOW/HIGH)
Stockage en colonne dans des Compression Unit, utilise plus de CPU (surtout en HIGH)
Lors des inserts en direct-path seulement, le DML conventionnel remet les lignes en stockage ligne, donc avec moins bon taux de compression et chained rows.
QUERY: Valable pour des données qui ne sont pas modifiées, mais la lecture est rapide (l’économie d’I/O compense le surplus de CPU)

COLUMN STORE COMPRESS FOR ARCHIVE LOW/HIGH

Archive compression (aka Hybrid Columnar Compression ARCHIVE LOW/HIGH)
Stockage en colonne dans des Compression Unit, utilise plus de CPU (surtout en HIGH)
Lors des inserts en direct-path seulement, le DML conventionnel remet les lignes en stockage ligne, donc avec moins bon taux de compression et chained rows.
ARCHIVE: Valable pour des données qui ne sont pas modifiées et peu lues (car forte utilisation CPU pour décompresser aussi)

12c: des noms de Wait Events plus parlants

Certains noms de wait events sont trompeurs. C’est le cas par exemple de ‘db file sequential read’ qui n’est pas ce qu’on entend par lecture disque séquentielle, mais plutôt une lecture monobloc (donc plutôt des i/o randoms).
Alors que ‘log file sequential read’ est bien une lecture de plusieurs blocs contigus.

La doc précise leur définition, mais en 12c on a aussi un ‘Display Name’ qui permet à Oracle de montrer un nom un peu plus parlant.

Voici ceux dont le ‘display name’ est différent du nom de l’event:

select wait_class,name, display_name from v$event_name where display_name != name order by 1,2;
WAIT_CLASS NAME DISPLAY_NAME
Administrative concurrent I/O completion online move datafile IO completion
Administrative datafile copy range completion online move datafile copy range completion
Administrative wait for possible quiesce finish quiesce database completion
Commit log file sync commit: log file sync
Configuration log buffer space log buffer full – LGWR bottleneck
Idle LGWR real time apply sync standby apply advance notification
Other DFS db file lock quiesce for datafile offline
Other Image redo gen delay redo resource management
Other datafile move cleanup during resize online move datafile resize cleanup
System I/O control file sequential read control file read
System I/O control file single write control file write
System I/O db file parallel write db list of blocks write
System I/O log file parallel write log file redo write
System I/O log file sequential read log file multiblock read
System I/O log file single write log file header write
User I/O db file parallel read db list of blocks read
User I/O db file scattered read db multiblock read
User I/O db file sequential read db single block read
User I/O db file single write db single block write

La différentiation des i/o single block et multiblock est particulièrement intéressante.

12c: optimisation des appels de fonctions PL/SQL à partir de SQL

Dans les requêtes SQL, on peut utiliser des fonctions standard (substr, to_date,… ) et lorsqu’on a un besoin un peu plus spécifique, on peut créer des des UDF (User Defined Function).
Avec Oracle, les fonctions standard sont codées en C, directement exécutable lors de l’exécution SQL. Mais les UDF sont simplement des appels à des fonctions PL/SQL stockées. Le problème lorsqu’on passe d’un language (SQL) à un autre (PL/SQL) en cours d’exécution: il y a un switch de contexte à chaque fois. C’est très consommateur de CPU. Surtout lorsqu’on appelle la fonction pour chaque ligne.

Jusqu’en 11g, les solutions sont:

  • Utiliser seulement des fonctions standard
  • Eviter l’appel ligne à ligne avec les pipelined functions
  • Lorsque le résultat de la fonction est déterministe et qu’il est applelé plusieurs fois pour les même valeurs, déclarer la fonction en deterministic

En 12c on a des vrais UDF optimisées pour les appels à partir de SQL.

On peut utiliser le pragma UDF pour une fonction stockée, par exemple:

create or replace function TEST_SPELL(n number) return varchar2 as pragma UDF; begin return to_char( date'-4712-01-01'+n-1,'Jsp'); end;
/
select max(spelled) from ( select TEST_SPELL(num) spelled from TEST);

Et on peut aussi déclarer la fonction comme un bloc PL/SQL anonyme dans la requête SQL:

with function TEST_SPELL(n number) return varchar2 as begin return to_char( date'-4712-01-01'+n-1,'Jsp'); end;
select /*+ TEST1 */ max(spelled) from ( select TEST_SPELL(num) spelled from TEST)
 /

Dans la demo j’appelle une fonction simple pour 1 million de lignes
Dans la version 11g, les temps ‘sql execute elapsed time’ et ‘PL/SQL execution elapsed time’ sont considérables, alors que ni la requête ni la fonction ne sont très complexes.
C’est parce chacun inclut le switch de contexte vers l’autre language.
En utilisant pragma udf ou le with plsql (déjà exposé ici) c’est une centaine de secondes que l’on gagne pour les 1 million d’appels.

12c: colonnes auto-incrémentées

En 12c on peut déclarer des colonnes auto-incrémentées en suivant la syntaxe SQL standard:

create table TEST ( id number generated always as identity , n number );

Plus besoin de déclarer la sequence, c’est implicite. Et cette syntaxe empêche de mettre une autre valeur que celle qui sera générée.

En 12c on peut aussi utiliser une sequence comme valeur par défaut (générée seulement lorsqu’on ne spécifie pas la valeur). et la syntaxe pour les colonnes quto-incrémentées est la suivante:

id number generated by default as identity

Enfin, nouvelle feature en 12c, on peut préciser si la valeur par défaut remplace un null:

id number generated by default on null as identity

Les exemples dans la demo

Attention, la colonne est NOT NULL, mais ne déclare pas de primary key (ni d’unicité) implicitement.

12c: faibles améliorations sur les verrous en intégrité référentielle

En 11g les verrous Row-S posés sur la table opposée lors de DML sur une table ayant un intégrité référentielle sont devenus des Row-X
Et cela a amené pas mal de régression: lorsqu’il y a des tables verrouillées en Share (à cause de foreign key non indexées) l’impact est devenu beaucoup plus important. Cette régression a été introduite pour corriger un bug: Bug 5909305 – Change to DML (TM) lock modes for foreign key constraints (Doc ID 5909305.8)

Il semble qu’en 12c ce soit un peu mieux: Certains Row-X redeviennent des Row-S, verrous légers qui ne bloquent aucun DML.

  • Sur insert dans table parente. C’est maintenant un Rows-S qui est posé sur les tables filles, comme avant la 11g
  • Lorsqu’on fait un delete sur une table fille on retrouve un Row-S sur la table parent comme avant la 11g

Par contre, on a toujours un Row-X sur la table parent lorsqu’on insert dans la table fille ou qu’on update la foreign key.

La liste complète des verrous posés par différentes opérations dans la demo.

Les explications de ces verrous dans la préz (11g): http://prezi.com/uzdd5ttg4cu0/indexing-foreign-keys-in-oracle/

Et une explication sur les modes de verrous: http://www.soug.ch/fileadmin/user_upload/Newsletter/NL_public/NL_2013_1_Award_Article.pdf et http://prezi.com/cdckwsgqxeyi/oracle-table-lock-modes/

12c: ‘update global indexes’ enfin très rapide sur drop et truncate

On partitionne les tables pour 2 raisons:

  • Performance: aller lire un plus petit segment en full scan, ou un plus petit index local grâce au partition pruning
  • Maintenabilité: faire des opérations de purge, archivage, reorganisation, voir chargement (avec exchange partition) de manière très facile

Mais toutes les requêtes ne font pas de partition pruning, et on est amené créer des index globaux pour des raisons de performances. Mais alors on perd de la maintenabilité: les opérations sur les partitions vont:

  • soit rendre l’index unusable, qu’il faudra rebuilder complètement. Ce n’est pas acceptable lorsque notre maintenance ne touchait qu’une partition parmi beaucoup d’autres
  • soit utiliser ‘update global indexes’ pour maintenir l’index au fur et à mesure. Mais l’opération devient alors très longue.

Et pourtant, s’il s’agit d’un DROP PARTITION ou TRUNCATE PARTITION, cette maintenance d’index consiste seulement à nettoyer les entrées d’index qui deviennent orphelines (i.e qui adressent un rowid qui n’existe plus).

Alors la 12c a optimisé cela en faisant l’opération en 2 temps, utilisant la fonctionnalité ‘Asynchronous Global Index Maintenance’ ou ‘Fast Index maintenance':

  1. un ALTER TABLE … DROP/TRUNCATE PARTITION UPDATE GLOBAL INDEXES
  2. ne va pas toucher aux index globaux. Il va seulement enregistrer le data_object_id du segment qu’on vient de supprimer (un truncate ne supprime pas vraiement le segment, mais en change son data_object_id ce qui revient au même logiquement). C’est enregistré dans la table du dictionnaire SYS.INDEX_ORPHANED_ENTRY$.

  3. les requêtes qui lisent l’index chargent ces infos pour ignorer tous les rowid qui référencent ce data_object_id
  4. un job automatique (un de plus) va faire le nettoyage de manière asynchrone

On peut faire ce nettoyage nous même avec

ALTER INDEX ... COALESCE CLEANUP;

mais aussi appeler

exec dbms_part.cleanup_gidx;

qui va faire ce coalesce sur tous les index référencés par

SYS.INDEX_ORPHANED_ENTRY$

Différentes variations dans la demo.
Le cleanup peut charger la machine, c’est peut-être mieux de l’anticiper à un moment de faible charge plutôt que de le laisser tourner à 2 heures du matin comme c’est planifié par défaut.

C’est une amélioration importante: en Entreprise Edition, il n’y a plus à hésiter pour partitionner les tables qui grossissent afin que l’augmentation du volume de l’historique ne pénalise pas les performances. Sans avoir peur de définir trop d’index global. Une fois partitionné, ce sera facile de maintenir les plus anciennes partitions: purger, archiver, déplacer sur des tablespaces (move online en 12c) qui sont sur des disques moins chers. Et plus on laisse les tables grossir, plus l’opération de partitionnement sera compliquée !

12c: inclure une fonction PL/SQL dans la requête SQL

En 12c, on peut mettre une fonction PL/SQL dans la clause WITH d’une requête SQL.
Par exemple:

with
     function to_number_or_null(n varchar2)
     return number deterministic
     is
      begin
       return to_number(n);
      exception
       when others then return null;
      end;
select * from v$parameter where name='db_block_size' and to_number_or_null(value)=8192;
/

Ici j’ai déclaré une fonction vite fait qui m’évite de récupérer un ‘ORA-01722: invalid number’ lorsque la valeur n’est pas un nombre.

On s’aperçoit que l’exécution est beaucoup plus rapide lorsqu’il y a beaucoup d’appels. (cf. demo). C’est plus rapide que d’appeler une fonction stockée – sauf pour une fonction deterministic qui bénéficie du cache.

A noter: le temps d’exécution n’est pas comptabilisé dans ‘PL/SQL execution elapsed time’ du Time Model

Attention: certains clients (comme sqlplus 11g) posent des problèmes à cause des points-virgules dans la requête.

12c Nouvelles opérations online

On a vu une opération online sur les fichiers: 12c: déplacer un datafile online et la 12c amène plusieurs nouvelles opérations sur les tables qui peuvent être faites online.

Commençons par celles qui ne nécessitent pas le mot clé ‘ONLINE’ et qui sont donc possible en Standard Edition: on peut rendre un index visible/invisible sans poser de verrou DML.

Et les opérations qui peuvent se faire avec ‘ONLINE’ en Enterprise Edition:

ALTER INDEX ... UNUSABLE ONLINE;
DROP INDEX ... ONLINE;
ALTER TABLE ... DROP CONSTRAINT ... ONLINE;
ALTER TABLE ... SET UNUSED(...) ONLINE;
ALTER TABLE ... MOVE PARTITION ... ONLINE;

Le MOVE d’une table non partitionnée ne prends pas le ‘ONLINE’, mais on pouvait déjà le faire avec dbms_redefinition.

Les exemples avec les modes de verrous exacts posés par ces opérations dans la demo.

La plupart de ces opérations posent seulement un Row-S et c’est vraiment online puisque seulement les DDL exclusifs sont bloqués/bloquants.
Mais certaines posent un Row-X qui peuvent être bloqués par des opérations DML (cas de la foreign key non indexée).