Chap 11
Chap 11
Chapitre 11
Optimisation des requêtes relationnelles
Une requête relationnelle formulée avec SQL est traduite en une expression algébrique représentée
sous la forme dʹune arborescence (arbre de requête) dans laquelle les noeuds internes sont les
opérateurs unaires ou binaires et les noeuds terminaux sont des relations de base ou des vues. Cet
arbre de requête est optimisé 1 avant dʹêtre transformé en un plan dʹexécution formé d’une suite
d’appels de procédures pour exécuter l’arbre optimisé. Le rôle de lʹoptimiseur du SGBD est de choisir
parmi tous les arbres équivalents possibles à une requête particulière, celui dont le plan dʹexécution
sera le plus performant. Comme le nombre dʹarbres est grand et que toutes les données ou facteurs
qui influencent le calcul dʹune expression ne sont pas connus à priori, le choix repose souvent sur une
heuristique générale dont lʹimplémentation peut varier dʹun optimiseur à lʹautre.
Avant de formuler cette heuristique générale dʹoptimisation, nous allons examiner les propriétés des
opérateurs relationnels qui sont à la base des transformations d’un arbre en un autre équivalent. Ces
transformations se font uniquement sur la base de règles syntaxiques 2.
Le MRD de la base OUC qui sera utilisé par les exemples de ce chapitre comprend trois relations:
Ouvrier (nas*, nom, salaire, spec, noUs)
Usine (noUs*, site, prod, nasChef)
Contrat(noCont*, fin, cout, noUs
L’attribut fin est de type Date. Le MCD ci-dessous est spécifié avec le formalisme UML. L’association
entre les classes Ouvrier et Usine est caractérisée par une multiplicité (auparavant les contraintes
structurelle du modèle E/As) : zéro ou plusieurs ouvriers travaillent dans au plus une usine.
Usine
0..1 noUs* 0..1
Tavaille site
prod Execute
0..*
nasChef
0..*
Ouvrier
nas* Contrat
nom noCont*
salaire fin
spec cout
noUs noUs
Source : http://www.ift.ulaval.ca/~agamache/pageperso/LivreBD/PageIntroLivreBD.html
André Gamache
Chapitre 11 Optimisation des requêtes SQL
Usine
(p) (p)
La présence d’un cycle est gênante, car elle bloque la création d’une usine si le chef qui est
aussi un ouvrier n’est pas créé et ce dernier ne peut pas être créé si l’usine où il travaille n’est
pas créée. Pour éviter ce cycle de contraintes, il suffit de ne pas définir la contrainte
d’inclusion au moyen du mécanisme de l’intégrité référentielle et de le faire avec un trigger
de Pre-Insertion. Ce MRD sans la contrainte d’inclusion sera le modèle type auquel nous
ferons référence dans ce chapitre sur l’optimisation des requêtes.
© A. Gamache 2
Chapitre 11 Optimisation des requêtes SQL
Par contre, la requête ci-dessous nʹest pas de ce type puisque lʹattribut nas est absent du prédicat.
SELECT nom, salaire
FROM Ouvrier
WHERE nom = 'Trudel' ;
© A. Gamache 3
Chapitre 11 Optimisation des requêtes SQL
Une telle requête est lourde à calculer puisquʹelle sous-tend la comparaison de tous les tuples de
lʹextension Ouvrier avec ceux de lʹextension Usine.
Bien entendu, une requête peut être à la fois du type intervalle et de groupement, comme une autre
peut être de type min-max et en même temps dʹintervalle. Les types de la requête prépondérants dans
une exploitation de la base ont une incidence sur la nature des index à créer et dans la manière avec
laquelle la réponse sera calculée. C’est cette opération de calcul de la réponse que l’optimisation
cherche à rendre rapide quelle que soit la catégorie à laquelle une requête appartient. Pour y arriver,
l’optimiseur transformera l’arbre de requête et fera un choix approprié d’un ou plusieurs algorithmes
de calcul qui exploiteront les index existants. Le résultat de cette opération est un plan de calcul
(Execution plan) qui fournit la réponse recherchée pour une requête.
La deuxième phase de l’analyse est de nature sémantique et vise à débusquer les prédicats toujours
faux, à transformer une requête imbriquée en une jointure classique et à vérifier la cohérence d’un
prédicat au regard des contraintes de domaine définies dans la dictionnaire. Par exemple, si le salaire
maximum d’un employé est limité à 100 000.00$, il ne sert à rien de lancer l’exécution d’une requête
dont le prédicat est le suivant : Where salaire => 150000.00. Lors de l’étape d’analyse sémantique, il est
possible de rejeter la requête sur la base seule d’une contrainte de domaine (Erreur de type 3).
Finalement, une troisième phase consiste à générer l’arbre de la requête formulé au moyen des
opérateurs algébriques. Cette opération quoique non obligatoire a l’avantage de fournir une
représentation algébrique de la requête SQL dont les transformations sont facilement comprises
© A. Gamache 4
Chapitre 11 Optimisation des requêtes SQL
puisque nous maîtrisons la sémantique des opérateurs de l’algèbre. Souvent dans les SGBD
commercialisés, cet arbre n’est pas explicitement généré et le système passe directement à la
formulation du plan d’exécution. Un grand nombre de SGBD implémentent les phases 1 et 2. Par
contre, la phase 3 n’est pas encore très bien mise en œuvre par la plupart des systèmes. À la sortie de
l’analyse le SGBD récupère un plan d’exécution logique représenté soit par l’arbre de requête, soit par
une suite d’appels de procédures spécialisées dont le résultat est la réponse demandée. Ce plan
d’exécution dit logique pourra être par la suite converti lors de l’étape d’optimisation en un plan
d’exécution physique composé lui aussi de programmes organisés en arbre. À la limite, un plan
d’exécution logique peut coïncider avec le physique.
2- Optimisation de l’arbre de requête
L’arbre de requête est transformé en un plan d’exécution physique composé avec les procédures de
calcul des opérateurs algébriques qui sont exécutées dans un ordre choisi pour être efficace. Ce choix
dépend aussi de l’indexation de certains attributs et des algorithmes sous-jacents à leur indexation. La
difficulté est de choisir parmi de très nombreux plans d’exécution possibles, celui qui dans le contexte
présent s’avèrera le plus efficace.
3- Exécution du plan
Le plan d’exécution obtenu de l’étape d’optimisation est ensuite compilé (plus précisément les
programmes appelés par les nœuds du plan) et lancé afin d’aboutir aux résultats escomptés.
Exemple : σ p2 ≡
R
σ p3
Figure 11.3
© A. Gamache 5
Chapitre 11 Optimisation des requêtes SQL
Exemple SQL :
SELECT nas, nom
FROM (SELECT nas, nom FROM Ouvrier WHERE salaire < 30 000)
WHERE nom = 'Gagnon' ;
Cette requête SQL sera plutôt exécutée par l’équivalent algébrique de la requête suivante :
Il en sera autrement lorsque la requête sous-tend des jointures. Dans ce cas, le prédicat conjonctif sera
distribué parmi des diverses sélections pertinentes à chaque extension participante à la jointure.
Il peut être utile dans certains cas de transformer une cascade de prédicats en une conjonction des
mêmes prédicats. Inversement, il peut utile de remplacer une conjonction de prédicats distincts par
une cascade de sélections dans laquelle l’ordre des sélections importe peu.
La requête ci-dessus est équivalente à la suivante dans laquelle les prédicats ont été commutés :
Toutefois, si le prédicat p peut être transformé en une conjonction de prédicats p1 et p2 de sorte que
tous les attributs de p1 soient inclus dans le schéma de R1 et que tous les attributs de p2 soient inclus
dans le schéma R2, alors on a l’équivalence suivante :
σp (R1 |x| R2) ≡ σ p1 ∧ p2 (R1 |x| R2) ≡ σ p1 (R1) |x| σ p2 (R1)
© A. Gamache 6
Chapitre 11 Optimisation des requêtes SQL
Voici un exemple dans lequel l’équivalence est exprimée entre deux arbres de requête.
Figure 11.4
Dans cette transformation par la commutativité, la sélection descend vers les feuilles, permettant ainsi
dʹeffectuer plus tôt dans le calcul la sélection des tuples et dʹobtenir une extension intermédiaire moins
encombrante qui, si la taille le permet, pourrait être rangée totalement dans la RAM. Cette stratégie
accélère le calcul en évitant les accès au disque.
Voici un exemple dʹune descente plus profonde de la sélection. Voici une requête SQL avec deux
jointures dont lʹune est exprimée sous forme dʹune sous-requête :
SELECT noCont
FROM Ouvrier O, Usine U
WHERE O.noUS = U.noUs and nom = 'Plante' and noUs = 10 And
noUS IN (SELECT noUS
FROM Contrat
WHERE fin > 'dec-97');
Cette requête initiale avec une sous-requête est transformée par l’analyseur en une jointure triple :
SELECT noCont
FROM Ouvrier O, Usine U, Contrat C
WHERE O.noUS = U.noUs And U.noUs = C.noUS And O.nom = 'Plante' and
U.noUs = 10 And C.fin > 'dec-97');
N.B. Cette requête est plus facilement représentée par un arbre de requête évitant l’usage d’un sous-
arbre dans le prédicat de jointure entre Ouvrier et Usine.
Lʹarbre de la requête est donc le suivant:
Π noCont
σ O.nom =’Plante’ And U.noUS = 10 And fin > ‘dec-1997’
Usine Ouvrier
Chapitre 11 Optimisation des requêtes SQL
Cet arbre de requête peut être transformé lors de l’optimisation en commutant la sélection avec la
jointure de sorte que la sélection soit exécutée le plus tôt possible dans le calcul de la réponse. En effet
une sélection réduit la taille de la table intermédiaire qui occupe moins d’espace disque ce qui entraîne
moins de lectures.
Π noCont
Figure 11.5
Dʹautres transformations algébriques sont possibles comme par exemple la permutation des jointures.
Celles-ci sont avantageuses, notamment si elles réduisent la taille des extensions intermédiaires dans
le processus dʹévaluation de lʹarbre de requête.
En effet la liste précédente doit obligatoirement contenir les attributs de la liste suivante. À la fin, seuls
les attributs de la dernière liste de projection seront présents dans la réponse.
Voici un exemple d’une requête SQL de ce type :
Select nas
From (Select nas, nom
From (Select nas, nom, salaire
© A. Gamache 8
Chapitre 11 Optimisation des requêtes SQL
From Ouvrier));
La transformation fournit une autre clause SQL équivalente, en principe plus simple à calculer.
Select nas
From Ouvrier;
Toutefois, si p = p1∧ p2 et que p1 contient que les attributs de R1 et si p2 est formulé quʹavec les
attributs de R2 alors on aura que :
Cas 2: Sʹil y a des attributs dans la condition de jointure qui ne sont pas dans la liste de sortie L, alors
ces attributs L’= {C1, C2, ...Ct} doivent alors être ajoutés à la liste L de la projection finale. Une
projection supplémentaire avec L est alors nécessaire afin de pouvoir transformer la projection initiale
de la jointure.
ΠL+L’ (R1 |x|R2) ≡ Π(A1, A2, ...An, C1, .. Ct) (R1) |x| Π(B1, B2, ...Bn, C1, ... Ck) (R2)
Exemple :
SELECT nom, salaire
FROM Ouvrier O, Usine U
WHERE O.noUs = U.noUs ;
Π nom, salaire
© A. Gamache 9
|X| Ouvrier.noUs = Usine.noUs
Ouvrier Usine
Chapitre 11 Optimisation des requêtes SQL
Figure 11.7
Lʹattribut noUs nʹest donc pas dans la liste de la projection et il y sera ajouté pour descendre la
projection et faire commutation avec la jointure .
Π nom. salaire
Ouvrier Usine
Figue 11.8
Cette transformation permet en théorie de diminuer la taille de lʹextension en diminuant la taille de
chaque tuple. Dans certains cas, cette transformation place beaucoup plus de tuples dans la zone de
mémoire partagée de sorte quʹil y aura accélération du calcul de la jointure. Rappelons encore une fois
que si le SGBD exécute en pipeline cette clause, la jointure de deux tuples est accompagnée de la
projection sur le ou les attributs de sortie.
© A. Gamache 10
Chapitre 11 Optimisation des requêtes SQL
R1 R2 R1 R2 R1 R2
(1) (2) (3) (4) (5)
Figure 11.6
L’exécution de cet arbre se fait de bas en haut. Il ne faut y avoir un calcul essentiel sériel. En effet, La
projection de R 1 est combinée la jointure pour que les deux opérations s’effectuent en parallèles.
Voici un exemple en SQL d’une telle transformation :
Select O.noUs, O.nom
From Ouvrier O, Usine U
Where O.noUs = U.noUs ;
Cette simple jointure pourrait être exécutée avec la projection des deux relations Ouvrier et Usine. En
principe, une telle transformation accélère le calcul si chaque opérateur est exécuté complètement
avant que le suivant soir lancé.
En pratique, cette transformation ne se fera pas, car la première forme peut être exécutée rapidement
en mode pipeline : un tuple de Ouvrier est comparé avec un tuple de Usine. Si la condition de jointure
est vérifiée, alors la projection du tuple est faite immédiatement et le résultat est stocké dans la table
de la réponse. On voit donc que seule la transformation algébrique est insuffisante pour obtenir une
exécution optimisée. Il faut aussi prendre en compte les algorithmes de calcul qui peuvent réaliser
plusieurs opérateurs dans une seule exécution. Ce rôle est dévolu à l’optimiseur qui se doit de
produire un PLAN D’EXÉCUTION de la requête formulée avec les algorithmes disponibles dans le
SGBD pour le calcul des requêtes.
© A. Gamache 11
Chapitre 11 Optimisation des requêtes SQL
∩ ≡ σp σp
R1 R2 R1 R2
∪
σp
≡ σp σp
∪
R1 R2 R1 R2
σp −
− ≡ σp σp
R1 R2 R1 R2
Figure 11.9
© A. Gamache 12
Chapitre 11 Optimisation des requêtes SQL
La relation ou la vue relationnelle OuvrierEnMission est présumée avoir été créée dans le dictionnaire
avec un schéma identique à celui de la relation de base Ouvrier. Dans cette requête la sélection est
effectuée en dernier et constitue ce qui et appelé le 2e bloc de la clause SQL.
La requête équivalente dans laquelle la sélection est effectuée en premier avant l’intersection.
SELECT nom
FROM Ouvrier
WHERE salaire > 50000 and noUs = 'u20'
INTERSECT
SELECT nom
FROM OuvrierEnMission
WHERE salaire >50000 and noUs = 'u20';
Voici un exemple similaire avec lʹunion. Cet opérateur exige aussi deux schémas compatibles pour
que le calcul soit exécutable. Dans ce cas-ci, le schéma des deux opérandes est compatible avec lʹautre.
L’un des opérandes est une vue relationnelle OuvriersEnConge.
SELECT nom
FROM Ouvrier
WHERE salaire > 25000 and salaire < 50000
UNION
SELECT nom
FROM OuvriersEnConge
WHERE salaire > 25000 and salaire < 50000 ;
Lʹassociativité est réalisée avec R1 et R2 puisque cette dernière relation, R2 comprend lʹattribut de
jointure de R1
R1 x (R2 x R3) + (R1 x R2) x R3
R1 ∪ (R2 ∪ R3) ≡ (R1 ∪ R2) ∪ R3
© A. Gamache 13
Chapitre 11 Optimisation des requêtes SQL
3- Réorganiser les feuilles de lʹarbre en utilisant lʹassociativité des opérateurs binaires de sorte que le
sous-arbre ayant la sélection la plus restrictive soit exécutée en premier.
4- Transformer toute suite formée dʹune sélection et dʹun produit cartésien en une jointure thêta.
5- Descendre le plus possible les projections vers les feuilles de lʹarbre de requête.
6- Identifier les sous-arbres ayant des opérateurs qui peuvent être exécutés en même temps que
dʹautres (mode pipeline), notamment la sélection. Par exemple, une sélection suivie dʹune projection
est une paire dʹopérateurs exécutables simultanément.
Quelques problèmes avec lʹheuristique générale
La restructuration dʹun arbre de requête sous-tend très souvent la permutation des opérateurs
binaires, notamment celui de la jointure. Or, ces permutations algébriques ne sont pas toujours
justifiées sur la plan de la performance. Au contraire la connaissance de la taille des extensions de base
et de celles des relations intermédiaires peut réduire les avantages de ces transformations. Par
exemple une suite de jointures peut être transformée en plusieurs arbres de requête différents et le
choix de lʹarbre optimisé à exécuter est difficile si on ignore la taille des extensions, les facteurs de
sélectivité pour les différents opérateurs et lʹindexation des attributs.
Exemple :
(Ouvrier |x| Usine) |x| Contrat
Si card(Usine) <<< card(Contrat) <<< card(Ouvrier) alors lʹexpression ci-dessous a des chances dʹêtre
plus performante pour le calcul de la réponse.
( Usine |x| Contrat) |x| Ouvrier)
© A. Gamache 14
Chapitre 11 Optimisation des requêtes SQL
La taille de la relation intermédiaire résultant par exemple la jointure (Usine |x| Contrat) est plus
petite si la sélectivité de lʹattribut de jointure dans Contrat est près de 1 (grande sélectivité). Elle sera
vraisemblablement plus rapide à calculer. La sélectivité de lʹattribut de jointure dans chaque relation
interviendra donc dans le choix de la permutation de jointures. Toutefois, la cardinalité des extensions
et les sélectivités des attributs ne sont pas les seuls facteurs qui conditionnent la rapidité du calcul. La
présence dʹun index sur lʹattribut de jointure de la relation Ouvrier augmentera de façon significative
la vitesse de calcul de la relation intermédiaire. Cette nouvelle donne, le SGBD peut la découvrir à
partir des métadonnées stockées dans le dictionnaire. Il se peut fort bien qu’un index compense pour
lʹencombrement et fait en sorte que lʹexpression initiale soit celle qui est retenue comme étant lʹarbre
optimisé.
* indexation de A dans R1
R1 R2 R1
Figure 11. 10
Or, si lʹopération de sélection est effectivement descendue le plus bas possible vers les feuilles et
appliquée avant la jointure, le résultat est une relation intermédiaire qui n’est plus indexée, car un
index du dictionnaire pointe sur les tuples dʹune table de base dont il connaît lʹemplacement. Par
conséquent, même si lʹattribut est indexé dans la table de base, celui-ci ne peut pas être utilisé dans la
table intermédiaire. Sʹil arrive que lʹattribut descendu soit celui de la jointure, alors cette descente
inhibant lʹutilisation de lʹindex dans le calcul de la jointure, autant alors l’éviter et faire en pipeline la
jointure et la sélection.
Limite de l’optimisation algébrique
On voit donc que la seule optimisation algébrique est insuffisante et peut même conduire à des
ralentissements du calcul. De plus, les diverses transformations algébriques conduisent à de
nombreux arbres de requêtes possibles parmi lesquels lʹoptimiseur doit sélectionner celui qui
deviendra le plan à exécuter. Or, cette sélection ne donne pas toujours les résultats escomptés parce
que des informations manquent pour guider ce choix. Ces métadonnées manquantes sont notamment,
la cardinalité des extensions, la sélectivité des attributs et la présence des index. Ces informations
seront à la base même de lʹoptimisation fondée sur le coût.
Modèle de coût
Pour estimer la taille des résultats intermédiaires, lʹoptimiseur doit avoir accès aux métadonnées
suivantes:
© A. Gamache 15
Chapitre 11 Optimisation des requêtes SQL
a- La distribution des valeurs de chaque attribut, sinon il fut assumer lʹhypothèse simplificatrice dʹune
distribution uniforme pour les valeurs des attributs et de faire aussi lʹhypothèse que ces derniers sont
indépendants les uns des autres.
b- Dans une extension, le nombre de valeurs non null pour un attribut Ai est noté card(Ai);
c- Les valeurs min(Ai) et max(Ai) pour chaque attribut de lʹextension courante; cette métadonnée
permettra de connaître le nombre maximum de valeurs différentes possible présentes dans la table;
e- Le nombre de valeurs distinctes pour chaque attribut Ai dans une relation R, soit V(Ai, R).
Ces métadonnées stockées dans le dictionnaire permettront dʹavoir une meilleure approximation des
tailles des relations intermédiaires et cela, grâce à la sélectivité si associé à chaque opérateur. Pour un
calcul plus précis, il faudra sʹéloigner de la distribution uniforme présumée et calculer la distribution
des valeurs pour chaque attribut. Ce calcul plus lourd ne conduit pas toujours un gain significatif de
performance.
La probabilité pi est celle de trouver une valeur donnée dans une extension R ou la fraction des
tuples qui seront obtenus par un opérateur de sélection. Par exemple, lʹattribut sexe dans une
extension a la probabilité de 1/2. Par conséquent avec une distribution uniforme, la moitié des tuples
de lʹextension courante réfèrent à des hommes, tandis que lʹautre à des femmes.
Sélection
Soit lʹopération de sélection suivante: σp(R1) où le prédicat est formulé quʹavec lʹattribut A, soit p (A=
10). Le nombre de tuples dans la réponse est estimé par
card(σp (R1)) = P(A) ∗ card(R1)
avec P(A), la proportion des tuples de R1 qui vérifient le prédicat p(A). Or, cette proportion est la
probabilité quʹun tuple de R1 vérifie le critère de sélection p définie pour cet opérateur comme étant le
suivant : P(A) = 1/V(Ai, R1)soit le la sélectivité du prédicat de sélection.
card(σp (R1)) = P(A) ∗ card(R1) = card(R1)/ V(Ai, R1)= si * card(R1)
© A. Gamache 16
Chapitre 11 Optimisation des requêtes SQL
Pour une extension donnée, plus le nombre de valeurs distinctes est grand, plus la taille de la réponse
sera petite. Dans le cas dʹune distribution non uniforme, le calcul de V(A, R1) est fonction de la nature
de A et de la distribution courante. Si la distribution n’est pas uniforme, il faut avoir l’histogramme
maintenu dans le dictionnaire. Avec ce dernier, il est possible de connaître combien de tuples ont une
valeur donnée pour chaque attribut. La sélectivité d’un attribut pour un intervalle de valeurs peut
alors être calculée plus précisément.
Projection
Une projection sous-tend toujours la suppression des doublets dans sa réponse. Plus le nombre de
doublets est grand plus la réponse risque dʹêtre petite. Par exemple, la projection de la relation
Ouvrier sur lʹattribut sexe, ne fournira que deux valeurs. La liste des attributs peut être plus ou moins
élaborée.
a- Avec une liste formée dʹun seul attribut A de R1 qui en est aussi la clé.
card(Π(A) ) = card(R1)
où d est la proportion des tuples de R1 qui sont des doublets sur les deux attributs A, B. Or, comme
nous avons présumé une distribution uniforme, cette proportion d est valable pour toutes les paires
de valeurs qui apparaissent dans la table R1.
Jointure
La taille dʹune jointure est estimée grosso modo (approximation de 1er ordre) par la formule suivante :
R1 |x|R2 = Pj ∗ (card(R1) ∗ card(R2))
où Pj est la sélectivité de la jointure.
Chaque tuple t de R1 est joint avec card(R2)/V(A,R2) tuples de R2, de sorte que nous avons au plus,
card(R1 |x| R2) = (card(R1) ∗ card(R2))/V(A, R2) = card(R1) ∗ card(R2) ∗1/V(A, R2)
card(R1 |x|R2) = card(R1) ∗ card(R2) ∗ Pj
© A. Gamache 17
Chapitre 11 Optimisation des requêtes SQL
et donc Pj = 1/V(A, R2). Toutefois, ce résultat suppose que tous les tuple de R1 (ou R2) participent à la
jointure.
La sélectivité de la jointure est estimée comme étant égale à lʹinverse du nombre de valeurs distinctes
de A dans la 2e relation opérande de la jointure. Cette sélectivité est généralement calculée avec les
statistiques recueillies dans le dictionnaire des données. Elles sont mises à jour périodiquement avec
ou sans l’intervention de l’administrateur de la base de données.
Il y a donc un avantage théorique avec les hypothèses avancées à exécuter une jointure en plaçant
comme premier opérande la relation ayant la plus petite taille. Ces estimés de Pj et les tailles des
extensions courantes varient avec la mise à jour des tables. Ces données sur les tables sont des
métadonnées rangées dans le dictionnaire de données du SGBD.
Ces métadonnées sont actualisées périodiquement par lʹexécution de la commande ANALYZE (pour
Oracle), RUNSTATS (pour DB2-V2) et UPDATE STATISTICS (pour IBM-Informix)
Cette approche ne tient pas compte des structures de données et de la charge dʹentrée-sortie
nécessaire pour traiter les tuples des relations. Il y a des structures telles les clusters, les index et le
hashing qui accélèrent passablement lʹaccès aux tuples et de ce fait allègent le calcul de la jointure.
Lorsque le plan dʹexécution estimé le plus efficace a été choisi, il est alors exécuté en faisant appel aux
algorithmes de base pour calculer la sélection, la projection et la jointure. Ceux-ci sont implémentés en
mettant à contribution les ressources présentes comme les index (cluster/non-clustered et dense/sparse),
les clusters et le hashing. Le choix dʹun algorithme pour calculer une jointure dépendra donc des
structures de données existantes au moment du calcul.
© A. Gamache 18
Chapitre 11 Optimisation des requêtes SQL
Lʹabsence dʹun index nʹest pas nécessairement catastrophique lorsquʹil sʹagit dʹattributs de faible
sélectivité (i.e. que pour une valeur de lʹattribut, beaucoup de tuples sont à vérifier). Il peut être aussi
rapide de parcourir séquentiellement une extension de la relation de taille raisonnable dont une partie
est en mémoire. Finalement, comme les pages contiennent que des tuples de même schéma logique, le
balayage total de lʹextension utilisera un minimum de pages.
Exemples :
La requête dʹintervalle ci-dessous peut être traitée efficacement même en lʹabsence dʹun index.
SELECT nas, nom
FROM Ouvrier
WHERE salaire > 15000.00 ;
En effet, on peut estimer avec les métadonnées que 85% des ouvriers ont un salaire supérieur à
15000.00 et quʹun balayage séquentiel sera plus efficace. Le seuil pour préférer le parcours séquentiel
est souvent fixé à à un facteur de sélectivité supérieur à 0.35.
Plan dʹexécution:
{
t : tuple de l’extension Ouvrier;
reponse m : set de_tuples; -- projection de Ouvrier sur
--attributs de sortie
Pour chaque tuple t de Ouvrier -- accès séquentiel
do
si t.salaire > 15000
alors reponse = reponse ∪ t[nas, nom]
fin_do
fin;}
Le plan d’exécution repose essentiellement sur un parcours séquentiel de la table. Lʹefficacité de cet
algorithme dépendra essentiellement de la taille de l’extension de la table Ouvrier.
b- Sélection avec index
Il peut y avoir, dans un prédicat de sélection, plusieurs attributs qui sont déjà indexés par autant de B-
arbres et dont lʹadresse de la racine de chaque arbre, rappelons-le, est stockée dans le dictionnaire du
SGBD. Il faut généralement faire un choix préalable pour exploiter dans le calcul que les attributs
indexés qui sont les plus sélectifs, i.e. ceux dont le facteur de sélectivité se rapproche de 1. Souvent, le
SGBD retient que les attributs dont la sélectivité dépasse un seuil donné. Les autres attributs seront
testés après lʹaccès aux tuples identifiés ou obtenus par un balayage séquentiel des tuples
éventuellement identifiés par lʹindex. Lʹorganisation de lʹindex disponible a aussi un impact sur la
performance du calcul. Un index dense/non-clustérisé sera moins performant quʹun autre
dense/clustérisé.
Ainsi, si le prédicat est formé dʹune conjonction d’attributs, le SGBD construira une liste dʹadresses
avec chaque attribut indexé, pour ensuite faire lʹintersection des rowid obtenus. Le résultat fournira les
adresses de tuples dont lʹaccès direct permettra de vérifier les autres attributs non indexés.
© A. Gamache 19
Chapitre 11 Optimisation des requêtes SQL
Rappel:
Un index dense est un index sur un attribut dont chaque pointeur conduit à un tuple. Au contraire, si
le pointeur conduit à un lot de tuples (ex. une page, lʹindex est dit non dense.
De même, si les entrées voisines dans lʹindex conduisent à des tuples qui sont à proximité voire dans
la même page, lʹindex est dit regroupé i.e. clustérisée. Au contraire, il est dit non clustérisé (sparse)
Algorithme SIDX2
Indexation de deux attributs du prédicat : (index sur salaire et une autre sur noUs)
SELECT nas, nom
FROM Ouvrier
WHERE salaire >= 25000.00 and noUs = 'u20'
and nom = 'Tremblay';
Plan dʹexécution :
Deux index sont utilisés : index idx_salaire et idx_noUs déjà définis sur la relation Ouvrier et
répertoriés dans le dictionnaire. La réponse est calculée sans avoir recours dans une première étape
aux tables.
{ --SIDX2
S, U, I set de rowid de Ouvrier;
S = Lire (idx_salaire_Ouvrier, 25000);-- set de rowid
U = Lire(idx_no_us_Ouvrier, 'u20'); -- set de rowid
I = Intersection (S, U); -- pour critère ET
Pour chaque rid de I -- rid est une adresse de tuple
do
{
t = Lire (Ouvrier, rid) --lire directe de t qui est un tuple
-- de Ouvrier
si t.nom = 'Tremblay' alors
reponse = Union ( reponse, (t.nas, t.nom));
fin_do;
fin;
Lorsque les critères sont liés par un OU, alors il y aura Union des ensembles S et U. Certains systèmes
adoptent une stratégie efficace soit dʹexploiter un index et de vérifier les autres critères en même
temps que lʹaccès aux tuples.
Une autre méthode encore plus efficace consisterait à lire dans le B-arbre la première feuille pour la
valeur 25000.00 et parcourir horizontalement les autres feuilles jusquʹà la dernière en vérifiant pour
chaque tuple si les autres termes du prédicat conjonctif sont vérifiés. Ce balayage via lʹindex est
efficace si la sélectivité de lʹattribut est assez grande.
Jointure sans index
© A. Gamache 20
Chapitre 11 Optimisation des requêtes SQL
La jointure peut être calculée par différents algorithmes selon la présence ou lʹabsence dʹindex sur les
attributs de jointure. En lʹabsence dʹindex, le calcul est fait en exploitant au maximum la structure
particulière du placement des tuples parmi les pages. Dans une jointure de type R1 |x| R2, nous
ferons lʹhypothèse que le premier opérande est toujours formé avec la relation ayant la plus petite
extension.
a- Algorithme de jointure par boucles imbriquées (JBI)
Cet algorithme est le plus simple, mais souvent le moins efficace avec des extensions de grandes
tailles. La première extension est lue séquentiellement et de préférence stockée entièrement en RAM.
Pour chaque tuple de R1 en RAM, il y a une comparaison avec les tuples de la deuxième extension,
R2. Soit np(R1) le nombre de pages utilisées pour stocker les tuples de R1, alors le coût de ce calcul est
approximé par :
Coût-JBI = np(R1) + np(R1) * np(R2)
Le coût est composé des pages de R1 lues initialement pour les placer en mémoire. Ensuite, pour
chaque page de R1 il faut lire toutes les pages de R2.
Soit les deux attributs de jointure A et B des relations R1 et R2, alors lʹalgorithme BI est le suivant :
{ -- JBI
Pour chaque tuple de R1
{do
Lire (R1, t1);-- t1 est une var. de tuple
Pour chaque tuple de R2
{do
Lire (R2, t2);
si t1.A = t2.B alors
reponse = t1 || t2 ;}}}
Cet algorithme est dʹautant plus coûteux que le nombre de pages de R2 est élevé.
b- Jointure par tri-fusion (JTF)
Les deux extensions sont en premier triées sur les deux attributs de jointure et ensuite les tuples triés
sont fusionnés pour retenir que les tuples ayant la même valeur pour les attributs de jointure. le coût
du tri est de lʹordre de nlog2n avec n égale au nombre de pages dʹune extension. Le traitement des
tuples en RAM est considéré comme négligeable.
Le coût de cet algorithme est composé des coûts de lecture des pages en mémoire :
coût-JTF = np(R1)*log2(np(R1)) + np(R2)*log2(np(R2)) + np(R1) + np(R2)
Cet algorithme est plus performant que le précédent. Son efficacité dépend essentiellement de celle
du tri utilisé (ex. Quicksort).
{-- Algo JTF
R1 = Trier (R1, A);
R2 = Trier(R2, B);
reponse = fusion de R1 et de R2;
fin;
© A. Gamache 21
Chapitre 11 Optimisation des requêtes SQL
}
c- Jointure par hashing (JH) : équijointure
Lʹidée de base est lire séquentiellement les tuples de la plus petite extension, R1 et de traiter par
hashing la valeur de lʹattribut de jointure A. Aucun attribut de la jointure n’est indexé ou si la
sélectivité de l’attribut de jointure est très grande. Tous les tuples de R1 qui ont une même valeur de
hashing sont placés dans une même page temporaire. Celle-ci est rappelée lors du calcul de la
jointure. Lors du hashing, les pages sont gardées le plus longtemps en mémoire RAM afin de pouvoir
y ranger, dans un deuxième temps, les tuples de R2 qui ont la même valeur de hashing.
t2 de R2
2 pages de
H(t2[B])
données
t1 de R1
H(t1[A])
1 ZMP
Figure 11.11
La deuxième relation R2 est lʹobjet du même traitement par hashing sur lʹattribut B. Dans une même
page, se retrouvent donc les tuples différents mais ayant la même valeur de hashing.
Hashing (R1, A, R2, B)
{ int p;
Pour chaque t1 de R1
do {
Lire (R1, t1)
p = h(t1, A);
Ecrire(page p, t1);
}
Pour chaquet2 de R2
do {
Lire (R2, t2);
Accéder(pagesR1, t2.B);
si TRUE alors Ecrire(reponse, t1||t2);}}
Si cette opération de regroupement est effectuée lors du chargement initial de chaque tuple, le
regroupement par la valeur de lʹattribut de jointure se fait progressivement (clustering). Dans ce cas, il
faudra déclarer un cluster qui devrait sous-tendre la création de deux index denses et regroupés
(clustered), un pour A et un autre pour B. La jointure se fera alors par lecture des tuples stockés dans
les pages clustérisées. Si une page vient à déborder, il y a gestion du débordement par chaînage des
pages. Au moment de la jointure, il suffit donc de faire la jointure des tuples qui se retrouvent dans la
même page ou à proximité, en vérifiant quʹils ont la même valeur pour les attributs de jointure et cela,
© A. Gamache 22
Chapitre 11 Optimisation des requêtes SQL
pour éviter les jointures de tuples qui sont en collision. Cet algorithme peut être utilisé efficacement
qu’avec l’équijointure, i.e. un prédicat de jointure construit avec l’égalité.
Jointure sans clustering préalable
Lorsque les tuples ne sont pas au préalable regroupés (clustered), il faudra en premier effectuer cette
opération de lecture et de placement des tuples dans des pages temporaires. Cette opération est
lourde en terme de temps de lecture. Une fois les tuples regroupés, lʹalgorithme se poursuit avec le
calcul de la jointure.
Une variante de cet algorithme est proposée par Babb5 . Elle est intéressante dans les cas où le
clustering est à faire ponctuellement (dynamiquement), juste avant le calcul de la jointure. Il sʹagit de
construire et dʹutiliser un vecteur de bits qui signale la présence dʹun tuple ayant une valeur de
hashing dans la première relation. Le vecteur de bits est gardé en mémoire RAM.
R1 0 R2
1 Vecteur de bits
1
0
1
Tuple t de R1 0 Tuple t de R2
?
1
h(t[A] = x 1 h(t[B] = x
x est l’indice dans le vecteur 0 x est l’indice dans le vecteur
Figure 11.12
En calculant la valeur de hashing dʹun tuple de R2 et de son indice correspondant dans le vecteur, il
est possible de savoir rapidement si les deux tuples doivent être joints ou pas. En effet, si la valeur
obtenue correspondant à un bit 1, cela signifie quʹil peut exister un tuple de R1 ayant la même valeur
pour lʹattribut de jointure. Ce tuple est recherché en RAM ou sur disque. Lʹintérêt de cet algorithme
est que la lecture des tuples ne se fait que pour ceux dont on est certain quʹils seront joints. Au
contraire, si le bit est 0, alors le tuple de R2 peut être mis de côté ou ne pas être lu, car il ne peut être
joint à aucun tuple de R1.
Optimisation avec le SGBD Oracle
Il y a au moins deux modes dʹoptimisation disponibles et spécifiés dans le fichier des paramètres
INIT.ORA : OPTIMIZER_MODE = {RULE, COST}. Le mode RULE repose sur le calcul dʹune
pondération globale obtenue à partir dʹune table de poids pour chaque chemin dʹaccès que peut
prendre en compte le noyau du SGBD. Avec les versions plus récentes (version 7 et suivantes), la
mode par coût est devenu le mode par défaut et le nouvel optimiseur est aussi capable de générer des
plans dʹexécution performants avec ce mode. Les étapes sont le suivantes :
1- génération dʹun ensemble de plans qui reposent sur les chemins dʹaccès;
2- ordonnement des plans avec les statistiques obtenues par la commande ANALYZE;
© A. Gamache 23
Chapitre 11 Optimisation des requêtes SQL
Le plan d’exécution dʹune requête est accessible par lʹentremise de la table System.Plan_table
et la commande EXPLAIN PLAN.
EXPLAIN PLAN set statement_id = 'q1'
INTO [system.]Plan_table FOR
select * from Empl where age <= 30';
Pour lire le plan dʹexécution généré il suffit de le lire dans la table Plan_table :
SELECT operation, options, object_name
FROM system.Plan_table
WHERE statement_id = 'q1';
OPERATION OBJECT_NAME
SELECT STATEMENT
Table access Empl by ROWID
Index Empl_age_idx RANGE SCAN
Figure 11.13
Lʹindex sur âge est utilisé pour trouver le premier rowid du tuple correspondant à 30. L’opération qui
est suivie dʹun parcours séquentiel dans lʹindex pour trouver les autres adresses rowid qui
correspondent aux employés dont lʹâge et inférieur ou égal à 30 (Range Scan). Pour chaque adresse
(rowid ou rid) trouvée, il y a un accès direct au tuple correspondant.
Par contre, une jointure donnera un plan dʹexécution comme celui-ci :
SELECT U.site, C.noCont, C.fin
FROM Usine U, Contrat C
WHERE U.noUs = C.noUs;
Le plan affiché par la commande EXPLAIN PLAN sera le suivant:
© A. Gamache 24
Chapitre 11 Optimisation des requêtes SQL
Lʹindex sur la relation Usine sera utilisé en accédant à sa première entrée et avec ce premier rowid, le
tuple correspondant dans la table Usine est consulté. Ensuite, pour ce tuple, il y a aura un balayage
séquentiel de la relation Contrat.
Pour que cette approche soit suivie, il faut que les statistiques aient été générées pour les tables, les
index et les clusters utilisés par les requêtes. Si celles-ci ne sont pas disponibles, le système sera forcé
dʹutiliser une approche par les règles (RULE based). Les statistiques sont au départ celles par défaut et
elles sont mises à jour à chaque fois que la commande ANALYZE est lancée par le DBA.
ANALYZE TABLE Ouvrier COMPUTE STATISTICS;
ou
ANALYZE TABLE Ouvrier ESTIMATE STATISTICS SAMPLE 3000 rows;
Pour obtenir les statistiques produites, notamment le nombre de tuples, la longueur moyenne dʹun
tuple, le nombre de valeurs distinctes pour chaque attribut, la plus grande valeur et la plus petite, il
suffit de consulter les tables USER_TABLES, USER_TAB_COLUMNS, USER_INDEXES.
Exemple :
SELECT *
FROM USER_TAB_COLUMN
Lʹapproche par coût est modulée par un paramètre qui est le but recherché. Deux buts sont possibles :
ALL_ROWS (le défaut) et FIRST_ROWS. Le premier but sous-tend une recherche pour obtenir tous les
tuples de la réponse dans le meilleur temps possible. Le deuxième but imposé au SGBD de retourner
au moins un tuple dans le plus court délai possible.
Optimisation par les coûts
Cette optimisation repose sur l’importance des écritures et des lectures prévisibles à partir des
statistiques rangées dans le dictionnaire. La mise à jour de ces statistiques est de la responsabilité du
DBA. Plusieurs paramètres régissent le fonctionnement ce mode d’optimisation et doivent être
précisés par l’administrateur :
La collecte des statistiques est rendue possible par l’exécution de l’utilitaire ANALYSE qui obtient les
informations suivantes sur une table particulière de la base :
© A. Gamache 25
Chapitre 11 Optimisation des requêtes SQL
Ces informations sont conservées dans les tables du dictionnaire : DBA_TABLES, ALL_TABLES,
USER_TABLES qui sont accessibles à l’administrateur ayant les privilèges appropriés.
Les index peuvent aussi être analysés par l’utilitaire ANALYSE et les données obtenue stockées dans
les tables DBA_INDEX, ALL_INDEX, USER_INDEX.
Exemples :
ANALYSE TABLE Ouvrier COMPUTE STATISTICS FOR TABLE ;
ANALYSE TABLE Ouvrier COMPUTE FOR ALL INDEX ;
ANALYSE INDEX Ouvrier_idx COMPUTE STATISTICS ;
La table Plan_table doit être au préalable créée avec un schéma imposé par le SGBD. Le plan
dʹexécution de la clause SQL est optimisé et rangé dans cette table où il peut être relu par un SELECT.
Pour lire le plan dʹexécution de cette clause, il suffit donc de lire le tuple correspondant stocké dans la
table Plan_table:
La réponse identifie les objets utilisés et le genre dʹaccès effectué par le système.
Schéma de la table Plan_table pour le système Oracle
Le schéma de la table Plan_table est créé au préalable par le script Oracle UTLXPLAN.SQL. Lʹexécution
de ce script permet la cération de la table SYSTEM.Plan_table dont la structure, i.e. son schéma,
comprend obligatoirement les attributs ci-dessous :
Attribut Sémantique
© A. Gamache 26
Chapitre 11 Optimisation des requêtes SQL
Le calcul dʹune requête est accéléré par lʹintervention de lʹoptimiseur qui agit au niveau syntaxique et
au niveau des coûts dʹexécution. Cette optimisation est généralement réalisée automatiquement, bien
que lʹutilisateur puisse fournir des directives (hints) à lʹoptimiseur pour diriger son action, soit en le
forçant à utiliser un index, soit en bloquant son utilisation. De plus, lʹutilisateur peut forcer
lʹoptimiseur à agir seulement au niveau syntaxique, et vice versa, seulement au niveau des coûts.
Au delà de ces actions, lʹutilisateur peut aussi agir en optimisant la formulation de sa requête et de ce
fait, en assistant lʹoptimiseur dans la recherche du meilleur plan dʹexécution.
Mise au point des requêtes SQL
© A. Gamache 27
Chapitre 11 Optimisation des requêtes SQL
L’utilisateur peut aussi lors de l’écriture d’une requête peut aider l’optimiseur en évitant ou en
privilégiant certaines structures de clauses. En effet, certaines écritures d’une clause bloque ou pas
lʹusage de certaines ressources comme par exemple les index. Lʹutilisateur doit penser à la mise au
point de sa requête lorsque les statistiques affichent un nombre dʹaccès disque fort important ou que le
plan dʹexécution lʹinforme quʹun index existant nʹest point utilisé. Cependant, il ne sʹagit pas dʹune
étape dans le processus dʹoptimisation, mais davantage le développement de meilleures façons de
formuler des requêtes SQL dictées par l’expérience du DBA.
Les exemples examinés par la suite sont formulés avec les relations de la BD :
Ouvrier (nas*, nom, salaire, spec, noUs)
Usine (noUs*, site, prod, nasChef)
Contrat(noCont*, fin, cout, noUs
Finalement, toute comparaison avec l’indicateur NULL supprime généralement lʹusage de lʹindex
puisque cette valeur nʹest pas indexée.
SELECT nas nom
FROM Ouvrier
WHERE salaire is NULL;
Cette requête peut engendrer inutilement un tri puisque le nas est une clé de Ouvrier. Elle devrait être
modifiée ainsi :
SELECT nas
FROM Ouvrier
WHERE spec = 'soudeur';
Si lʹintention de lʹusager était dʹavoir une réponse triée, il suffit alors dʹajouter le ORDER BY nas. Plus
généralement, sʹil y a une clé parmi les attributs affichés, le DISTINCT nʹest pas nécessaire.
Vérification du traitement dʹune jointure formulée avec une sous-requête
Quelques systèmes ne peuvent pas déceler une jointure dans la requête imbriquée suivante :
SELECT nas
FROM Ouvrier O
WHERE O.noUs IN (SELECT noUs
FROM Usine U);
Si le plan dʹexécution fournit par le SGBD ne traite pas cette requête en utilisant lʹindex sur noUs, mais
effectue le calcul de la sous-requête en premier, suivie par le test dʹinclusion de noUs, il faudra
transformer la requête en une jointure explicite :
SELECT nas
© A. Gamache 29
Chapitre 11 Optimisation des requêtes SQL
Avec une telle requête, lʹusage dʹune table temporaire (Tempo) simplifie la formulation de la jointure.
SELECT min(salaire) as petitSal, spec INTO Tempo
FROM Ouvrier
GROUP BY spec;
SELECT nas
FROM Ouvrier O, Tempo T
WHERE O.spec = T.spec and O.salaire = T.petitSal;
Les requêtes peuvent être réécrites et fusionnées pour éviter de faire appel à une table temporaire :
SELECT nas, nom
FROM Ouvrier
WHERE salaire = (SELECT salaire FROM Ouvrier);
Pour calculer cette requête, lʹindex sur le salaire pourrait ne pas être utilisé en raison de la plage de
valeurs pour lʹattribut salaire dont la sélectivité est faible. Lʹextension de Ouvrier sera de préférence
balayée entièrement et un tri sera fait sur les tuples sélectionnés. Un tel tri coûteux pourrait être
effectué pour chaque question similaire. Pour éviter ce tri coûteux, il suffira de créer une table
temporaire ou encore mieux une vue matérialisée :
SELECT nas, nom INTO Tempo FROM Ouvrier
WHERE salaire >10000.oo and salaire < 99999.00
ORDER BY nas;
La table Tempo est construite avec tous les tuples ordonnés sur le nas des ouvriers compris entre les
deux bornes de salaire qui ont été choisies à dessein pour inclure tous les ouvriers. Les requêtes
subséquentes pour afficher, en ordre, les ouvriers dont le salaire est compris dans une plage de
valeurs seront maintenant formulées sans tri sur la table Tempo.
SELECT nas, nom
FROM Tempo
WHERE salaire >20000.00 And salaire < 45000.00;
Avec une telle approche, un seul tri est exécuté sur les tuples stockés en ordre dans la table Tempo.
Toutefois, comme il sʹagit ici dʹune table temporaire, son extension nʹest pas automatiquement mise à
jour pour refléter les transactions en cours sur la BD. Les requêtes ci-dessus devront être exécutées sur
une période relativement courte pour avoir des réponses presque consistantes. Une autre façon pour
éviter ce problème est dʹutiliser de préférence un snapshot qui peut être mis à jour automatiquement et
donc, être en phase par rapport à lʹétat des tables de base.
© A. Gamache 31
Chapitre 11 Optimisation des requêtes SQL
Les requêtes sont parfois formulées pour obtenir une réponse correcte, sans le souci dʹobtenir la
clause la plus simple. Par exemple, la clause suivante donne le plus petit salaire payé pour la spécialité
ʹsoudeurʹ.
SELECT Min(salaire) as min_sal, spec
FROM Ouvrier
GROUP BY spec
HAVING spec = 'soudeur';
Cette requête ainsi formulée peut induire un tri qui nʹest pas esentiel pour le calcul de réponse.
Par contre, en modifiant la requête pour avoir la forme suivante :
SELECT nas
FROM Ouvrier
WHERE nom = 'Langlois' or spec = 'soudeur';
Si le plan dʹexécution exploite les index existants sur le nom et la spécialité, cette requête sera
performante. Au contraire, si les index ne sont pas utilisés, il faut alors supprimer le connecteur
logique OU et reformuler la requête avec lʹopérateur UNION. La nouvelle requête deviendra :
SELECT nas
FROM Ouvrier
WHERE nom = 'Langlois'
UNION
SELECT nas
FROM Ouvrier
WHERE spec = 'soudeur';
Cette réécriture peut être avantageuse lorsquʹil y a un index sur les deux attributs du prédicat
disjonctif.
Ordre des tables dans la clause FROM
Dans certains SGBD lʹordre des tables dans la clause FROM est significatif. Par exemple, un SGBD
pourra utiliser un index avec la première table du FROM et balayer la seconde. Si ceci est confirmé par
le plan dʹexécution, il est plus performant alors de balayer la plus petite extension, donc de la placer
obligatoirement en deuxième place dans le FROM.
© A. Gamache 32
Chapitre 11 Optimisation des requêtes SQL
Cette vue fournit les nas, nom et production des usines où travaille chaque ouvrier. Accessible aux
utilisateurs, elle peut engendrer inutilement des requêtes complexes.
SELECT spec
FROM OuvrierQc
WHERE nas = '3456';
Avec sa fusion avec la vue relationnelle, cette requête devient la jointure suivante :
SELECT spec
FROM Ouvrier O, Usine U
WHERE O.noUs = U.noUs and
site = 'Québec' and nas = '3456';
Cette jointure fournit la réponse attendue. Elle peut être aussi obtenue par une formulation plus
simple en exploitant le fait que lʹattribut specialité est inclus dans le schéma de la relation Ouvrier.
SELECT spec
FROM Ouvrier
WHERE nas = '3456' and noUs = 'Québec';
Exemples : les index sur le noEmpl et celui sur le tauxH ne seront pas exploités.
SELECT *
FROM Empl
© A. Gamache 33
Chapitre 11 Optimisation des requêtes SQL
SELECT *
FROM Empl
WHERE tauxH * .33 > 24.25 ;
À noter que dans la requête qui précède, un index sur l’attribut tauxH ne serait pas très utile, car
toutes les valeurs de tauxH doivent être lues et transformées afin dʹeffectuer la comparaison du
prédicat.
De même, il est aussi difficile de tirer profit de lʹindex sur le nom dans la clause ci-dessous, lorsque ce
dernier est inclus dans une expression de chaîne :
SELECT *
FROM Empl
WHERE Substr(Empl.nom,5,1) = 'S';
Dans certains exemples ci-dessus, il est possible de transformer la requête en une autre équivalente
qui ne bloquerait pas lʹusage de l’index.
Mise à jour des index
Malgré leur intérêt pour la recherche, la multiplicité des index présentent quelques inconvénients
pour les opérations de mise à jour et de suppression. Cela peut donner lieu à divers phénomènes de
ralentissement :
- un ralentissement suite à la mise à jour des entrées de tous les index qui font référence à lʹattribut de
la table modifiée. Il y aura éventuellement, un rééquilibrage du B-arbre;
- une occupation trop importante par les index, de lʹespace sur disque et dans les pages de la ZMP;
- une augmentation du taux de remplacement des pages (page swapping) en raison de la présence des
index, ce qui peut ralentir la recherche.
Par ailleurs, l’opération de suppression (DELETE) des tuples se prolonge par une suppression de
l’entrée correspondante dans lʹindex, ce qui ralentit celle qui concerne les tuples. L’opération
d’insertion est aussi pénalisée par la mise à jour de l’index, même si l’insertion ne sous-tend pas de
recherche dans une table. Mais, tout ajout dans une table indexée entraîne la mise à jour de l’index.
© A. Gamache 34
Chapitre 11 Optimisation des requêtes SQL
utilisés même s’ils existent et le SGBD fait un balayage complet. Le tri est fourni par le SGBD et
s’effectue dans un espace temporaire défini pour le système.
- GROUP BY
Cette clause sous-tend un tri interne dont le critère de classement inclut obligatoirement tous les
attributs du SELECT, sauf ceux utilisés comme argument dans une fonction. Aucun autre attribut
n’apparaît dans le SELECT.
or X.nom = 'Audy');
Pour traiter certains cas de négation, il est plus rapide pour l’optimiseur d’éviter le balayage
séquentiel d’une table en effectuant, lorsque c’est possible, les transformations automatiques suivantes
de l’opérateur de négation :
!> sera transformé en <= !>= sera transformé en <
!< sera transformé en >= !<= sera transformé en >
- Optimisation du connecteur logique OR
Les index ne sont utilisés que si les attributs du OR dans le WHERE sont tous indexés. Si ce n’est pas
le cas, il faut placer l’attribut le plus spécifique (sélectivité sj ) en première position de la cascade
logique.
Voici un exemple :
SELECT *
FROM Ventes
WHERE nom ='Ted'or nom ='Valérie' or article = 'a1' ;
Si le nom est indexé, mais pas l’article, alors l’index n’est pas très utile, puisque la table doit être
parcourue entièrement pour les articles.
- Clause ORDER BY
Après une sélection, les tuples sont triés par une procédure de tri incorporée au système Oracle.
L’argument du tri peut comporter plusieurs attributs et se faire en ordre descendant ou ascendant.
© A. Gamache 36
Chapitre 11 Optimisation des requêtes SQL
De façon générale, le calcul d’une jointure sera accéléré si les attributs de jointure sont indexés. Il faut
faire attention, cependant aux structures syntaxiques qui annulent l’usage des index comme par
exemple un prédicat avec une fonction :
a) Jointure sans attribut indexé : si aucun attribut indexé n’est disponible faire la jointure, le calcul est
fait avec un tri dynamique lancé par l’optimiseur;
b) Jointure avec index : s’il n’y a qu’un seul index de jointure disponible, la table non indexée est celle
qui est balayée en premier et doit pour cela apparaître en première position dans la clause FROM. S’il y
a un index sur les deux attributs de jointure, le noyau détermine le chemin d’accès selon une
pondération qui s’appuie sur l’indice de sélectivité des attributs de jointure (méthode heuristique).
Il est possible de spécifier à l’optimiseur de privilégier certaines règles ou lʹapproche par le coût, et
cela, au moyen des indices (hints) inclus sous forme de commentaire après le SELECT, DELETE et
INSERT. La directive à lʹoptimiseur est incluse dans la requête :
Cette clause est optimisée avec lʹapproche par coût, même si le plan possible avec lʹapproche par les
règles était plus performant.
Sachant quʹune extension sera balayée entièrement pour répondre à une question, il sera plus
performant dʹéviter lʹusage des index qui prennent de la place en mémoire et qui doivent être
parcourus. Pour cela, une information peut être transmise à lʹoptimiseur à savoir de faire un balayage
complet de la table Empl et dʹignorer tout index sur âge ou sur ville.
Conclusion
Références chapitre 11
1SMITH J.M., CHANG P.Y., Optimizing the performance of a Relational Algebra Database Interface,
CACM v. 18, no 10, pp. 68-79, 1975
© A. Gamache 37
Chapitre 11 Optimisation des requêtes SQL
© A. Gamache 38