Faut-il toujours indexer les clés étrangères ?

Pour actualiser un peu ce blog sur lequel je n’ai rien posté depuis 1 an, voici un lien sur une présentation (en anglais) sur l’indexation des clés étrangères (foreign key):
http://prezi.com/uzdd5ttg4cu0/indexing-foreign-keys-in-oracle/

Plutôt que de la traduire, je résume ici les idées principales pour aider à la lecture pour les francophones. Et n’hésitez pas à poser des questions ici.

Comportement d’Oracle lorsqu’il n’y a pas d’index sur la clé étrangère

L’idée qu’il faut toujours indexer les clés étrangères vient des anciennes versions où on se retrouvait avec un verrou table (TM-Share) lorsqu’on on modifiait une table.
Sous Oracle 7, c’est la table parent qui est vérouillée lorsqu’on insère un enregistrement dans la table fille. Puis à partir de la 8i c’est l’inverse: la table fille est vérouillée lorsqu’on supprime un enregistrement de la table parent.

Mais cela a été vite optimisé en 9i: ce verrou est relâché dès que la ligne parent est supprimée. Le verrou ne reste pas jusqu’à la fin de la transaction. C’est la raison pour laquelle on peut lire parfois qu’il n’y a plus de problème de verrou lorsque la clé étrangère n’est pas indexée. C’est faux. C’est juste que le verrou dure moins longtemps.

Mais il ne faut pas oublier que si la clé étrangère n’est pas indexée, alors la suppression d’une ligne parent devra faire un full scan de la table fille. Parce qu’il faudra vérifier que rien ne référence le parent qu’on veut supprimer. Et si la table fille est grosse, ça peut durer longemps. Et donc le verrou va quand même durer longtemps. Et pendant ce temps, toute modification sur la table sera bloquée, même si elle n’a rien à voir avec le parent qu’on est en train de supprimer.

Sur les version courantes d’Oracle il est donc nécessaire d’avoir un index sur la clé étrangère lorsque on a les 2 conditions suivantes:

  1. On peut supprimer (delete) une ligne de la table référencée (parent). C’est aussi le cas si l’on modifie (update) la colonne référencée. Mais on ne devrait pas modifier une clé normalement. On doit aussi penser aux cas des delete cascade, au cas de merge, etc.
  2. Et si la table fille est grande et/ou a beaucoup d’activité de modification dessus. Si ce n’est pas le cas, le verrou table n’aura pas une grande conséquence. Mais si c’est le cas, on peut bloquer toute l’application pendant un long moment.

En quoi un index peut aider ?

La première chose, c’est que l’index sur la clé étrangère permettra de vérifier rapidement qu’il n’y a pas d’enregistrements qui référenceraient la ligne qu’on supprime, sans avoir à faire un full table scan. Avant tout, on crée les index pour des raisons de performance d’accès.

La deuxième chose, c’est qu’en plus, lorsque Oracle va lire cet index, il va pouvoir vérifier qu’il n’y a pas d’insert en cours, même non encore commité. Parce que un insert va toujours dans un block bien précis dans un index.

S’il n’y a pas d’index, mais seulement la table pour vérifier, alors un insert peut aller n’importe où et il faut donc verrouiller toute la table pour empêcher cela. S’il y a un index, il suffit de verrouiller le bloc d’index correspondant à la valeur du parent pour la durée de l’opération de delete.

Quel index est possible ?

Si l’on crée systématiquement un index sur toutes les clés étrangères, on va se retrouver avec de nombreux index superflus. Parce ce que ce qu’il suffit d’avoir c’est un index qui commence par les colonnes indexées.
Je rappelle l’idée: si l’index permet de retrouver directement une (potentielle) ligne fille, alors il peut aussi servir à éviter de verrouiller toute la table.

Exemple:

Sur la table suivante:

table CHILD (a,b,c) ... references PARENT(a,b)

Les index suivants permettent d’éviter les verrous table lors d’un delete sur la table PARENT:

create index on (a,b)
create index on (b,a)
create index on (a,b,c)
create index on (a,b,c) compress
create unique index on (a,b,c)
create index on (a,b,upper(c))
CHILD is an IOT with primary key (a,b,c)

Par contre, ceux-là ne peuvent pas être utilisés pour ce but:

create index on (c,b,a)
create index on (a,c,b)
create index on (a,upper(b))
create index on (a,b desc)
create bitmap index on (a,b)

il est donc important de ne pas indexer aveuglément, mais de choisir les index qui sont utiles pour les performances des accés aux données. Ensuite, on vérifie qu’ils permettent aussi d’éviter les cas de verrou table s’ils peuvent avoir lieu.

Comment vérifier quels verrous sont posés ?

On a vu que le verrou est relâché dès que l’opération de suppression est finie. il ne reste pas jusqu’à la fin de la transaction. il est donc parfois difficile de le voir en test avec peu d’utilisateurs concurrents. On peut par contre utiliser l’event 10704 pour vérifier exactement quels verrous sont posés. La présentation montre un exemple avec son interprétation.

Conclusion et conseils

Lorsque une table parent peut avoir des delete dessus, même si c’est rare, même si c’est indirect (delete cascade) alors les tables filles peuvent se retrouver verrouillées s’il n’y a pas un index qui commence par les colonnes de la clé étrangère.
Si La table fille est grande et/ou si il y a beaucoup de modifications dessus, ce verrou peut durer et peut bloquer toute modification.
Dans ce cas, il faut indexer les clés étrangère. Mais pas aveuglément: on doit réfléchir à l’ordre des colonnes, à utiliser un index qui sert aussi à autre chose, etc.

Par contre, les clés étrangères vers des tables de référence statiques n’ont pas besoin d’index. Sauf bien sûr pour des raisons d’accès si on a besoin pour une jointure entre les deux table et que la sélectivité est assez forte pour bénéficier d’un index..

En test, on peut savoir exactement si une opération est susceptible de verrouiller la table fille avec l’event 10704 . C’est important car il y a quelques cas particuliers (des bugs par exemple). au moindre doute, il faut donc tester avec votre version d’Oracle, sur votre modèle de données.

En production, il faut réagir si on voit des wait eventsenq: TM‘ avec des verrous en mode 4 (Share) ou 5 (SSX). Même s’ils sont anodins maintenant, ils seront bloquants un jour lorsqu’il y aura plus de données ou plus d’activité.

Enfin, il ne faut pas voir la nécessité d’indexer certaines clés étrangères comme quelque chose de contraignant. Dans la plupart des cas l’index en question est nécessaire pour des raisons de performance, pour que le delete n’ait pas à faire un full scan de la table fille. Et le comportement d’Oracle est alors plutôt performant: aucun verrou n’est posé. Et cela depuis les plus anciennes version d’Oracle.

Un dernier point: si vous pensez que le mieux est de ne pas déclarer les contraintes d’intégrité référentielles en base de données, mais de les gérer au niveau de l’application, alors vous aurez des problèmes dès que vous aurez plusieurs utilisateurs concurrents. Soit vous risquez d’avoir une inconsistance de données, soit vous devrez verrouiller toute la table jusqu’à la fin de la transaction. Car les mécanismes interne d’Oracle que l’on vient de voir (relâcher le verrou avant la fin de la transaction, ou utiliser le bloc d’index pour voir les insert non commités) et qui sont très performants ne sont pas accessibles par l’application…

Droit au but en lisant un rapport AWR ou Statspack, par Franck Pachot

Il n’a a pas eu beaucoup d’articles traduits ces derniers mois. Pour patienter, voici un lien sur une présentation:
Interpreting AWR Report – Straight to the Goal en anglais, mais très visuel…

C’est parfois difficile d’aborder un rapport AWR ou Statspack qui comprends 50 pages de statistiques, dont seulement quelques unes sont utiles dans un contexte donné. J’ai vu plusieurs personnes qui ne savent pas vraiment par où commencer. Et sans une approche méthodique, c’est facile de perdre du temps sur des choses qui ne feront pas gagner un un temps significatif dans l’amélioration du temps de réponse.

Cette présentation montre une approche méthodique: en partant du temps passé en base de données (DB time) pour voir si le rapport est pertinent ou pas, puis en prenant les éléments du ‘Top 5 events’ en montrant où aller chercher les détails qui permettent de comprendre la raison d’un problème de performance, comment le résoudre, et estimer le gain en temps de réponse que peut apporter la résolution.

Unité de temps dans les vues V$

Historiquement, les vues V$ collectent les durées en centisecondes (100ème de secondes).
Cependant, certaines vues utilisent une autre unité:

microsecondes (1000000ème de seconde):

TIME_WAITED_MICRO, WAIT_TIME dans V$SESSION_WAIT, V$SYSTEM_EVENT, V$SESSION_EVENT, V$ACTIVE_SESSION_HISTORY
CPU_TIME, ELAPSED_TIME dans V$SQL, V$SQLAREA
WAIT_TIME dans V$LATCH, V$LATCH_PARENT, V$LATCH_CHILDREN
ACTIVE_TIME dans V$SQL_WORKAREA, V$SQL_WORKAREA_ACTIVE

millisecondes (1000ème de seconde):

CPU_WAIT_TIME dans V$ENQUEUE_STAT

secondes:

CTIME dans V$LOCK

La liste est non exhaustive, et les commentaires sont bienvenus pour la mettre à jour ;)

Opération INDEX JOIN (jointure entre indexes), par Franck Pachot

En général lorsqu’il y a plusieurs indexes qui peuvent répondre aux prédicats d’une requête, Oracle va choisir l’index le plus selectif, et les autres conditions seront vérifiées au moment d’aller lire l’enregistrement complet dans la table.
Ceci peut nous amener à créer un index concaténé, en combinant toutes les colonnes sur lesquelles il y a des prédicats, afin que l’index soit plus sélectif.

Mais ajouter un index a un coût et va pénaliser les insert/deletes ainsi que les updates qui touchent aux colonnes indexées.

Il y a cependant des cas où Oracle peut combiner deux indexes.
C’est le cas par exemple des indexes bitmap. On peut avoir un index bitmap sur chaque colonne, et Oracle va combiner les bitmaps avant d’aller voir la table (en utilisant des opérateurs binaire AND et OR sur les bitmaps). Mais ce n’est pas le sujet de cet article.

Il y a aussi un cas particulier avec les indexes ‘normaux': c’est le chemin d’accès ‘INDEX JOIN‘: les 2 indexes sont lus, et sont réunis, comme si l’on faisait une jointure sur le rowid, et la sortie de cette opération est équivalente à un index qui contiendrait toutes les colonnes.

Voici un exemple de cette opération ‘index join’, le but de cet exemple était de répondre à la question suivante, sur le forum dba-village: ‘Que signifient les indexes index$_join$_9 et index$_join$_8′ dans un plan d’exécution, et ‘comment les remplacer par mes propres indexes ?
Lire la suite

Description du mode de backup à chaud (BEGIN BACKUP), par Franck Pachot

Ce document tente d’expliquer exactement ce qui arrive lorsque vous utilisez ALTER TABLESPACE ... BEGIN BACKUP et ALTER TABLESPACE  ...  END BACKUP, et pourquoi il est obligatoire de l’utiliser lorsque la sauvegarde à chaud se fait avec un outil qui est extérieur à Oracle (tels que les sauvegardes faites à partir de l’OS utilisant cp, tar, la BCV, etc)
Il donne également une réponse aux questions suivantes, fréquemment posées:

  • Est-ce qu’ Oracle continue d’écrire dans les fichiers (datafiles) lorsque la tablespace est en mode backup ?
  • A quoi sert la commande ALTER DATABASE BEGIN BACKUP ?
  • Pourquoi le mode backup n’est pas utilisé avec RMAN ?
  • Que se passe-t-il si vous faites une sauvegarde sans avoir fait le BEGIN BACKUP ?
  • Que faire si l’instance se plante alors que vous êtes en mode backup ?
  • Comment vérifier quels datafiles sont en mode backup ?
  • Quels sont les archive logs minimum à garder avec la sauvegarde à chaud?
  • Pourquoi utiliser des backups par des commandes OS au lieu de RMAN?
  • Pourquoi la commande BEGIN BACKUP peut prendre du temps ?

Lire la suite