En 2018 aller vers le sujet suivant: TME Jointure
L'objectif de ce TME est de comprendre l'optimisation des requêtes qui contiennent des jointures.
Le TME dure 2 séances. Faire l'ex1 pendant la première séance et l'ex2 pendant la deuxième. L'ex3 est facultatif.
Ne pas oublier de consulter les questions fréquentes en bas de cette page
Lire l'énoncé de l'exercice dans le poly de TD: Exercice 3: Club de joueurs
commande | description |
---|---|
cd mon_répertoire | aller dans votre répertoire de travail |
tar zxvf /Infos/bd/public/tmeJointure.tgz | installer l'archive dans votre répertoire principal |
cd tmeJointure | aller dans le répertoire du TME |
emacs tmeJointure.sql & | éditer le fichier à compléter pendant le TME |
Alt-x my/sql-oracle ou Atl-x sql-oracle | se connecter à Oracle. Voir ConnexionOracle |
aller sur le paragraphe contenant @base3 et faire Ctrl-C Ctrl-C | créer vos tables J, C, F, les index et les statistiques nécessaires à l'optimisation basée sur le coût |
Les tables d'un club de sport sont :
qu'on abrège dans ce TME en :
Les index existants s'appellent: I_J_CNUM pour J(cnum), I_J_SALAIRE pour J(salaire), I_C_CNUM pour C(cnum), I_C_DVISION pour C(division) et I_F_CNUM pour F(cnum)
Il y a aussi la table BigJoueur(licence, cnum, salaire, sport, profil) dont l'attribut profil contient 4000 caratères.
Pour voir les plans proposés par le SGBD et leur coût :
SET autotrace trace EXPLAIN
Combien de n-uplets ont chacune des relations J, C, F et BigJoueur?
SET autotrace trace EXPLAIN SELECT * FROM J;
Quel est le coût d'accès à chaque table ?
le coût d'accès à un plan se lit dans la colonne Cost, pour la racine de l'arbre représentant le plan (première ligne).
On considère la requête R1.
SELECT J.licence, C.nom FROM J, C WHERE J.cnum = C.cnum AND J.salaire > 10;
Ecrire en français ce que retourne la requête R1
Afficher le plan de la requête R1 et constater qu'il s'agit d'une jointure par hachage. Dessiner l'arbre du plan d'exécution proposé par le SGBD. Numéroter les opérateurs de 0 à 3 (valeur du champ Id).
La jointure par hachage entre deux tables s'effectue en deux étapes :
a) Quel est le coût du plan ? Est-il égal à la somme des coûts du parcours séquentiel de J et C ?
Observer que le coût est le même lorsqu'on inverse l'ordre de lecture des relations : lire J avant C ou lire C avant J. Remarque, l'ordre est fixé par la clause FROM
quand on utilise la directive ordered
.
SELECT /*+ ordered */ J.licence, C.nom FROM J, C WHERE J.cnum = C.cnum AND J.salaire > 10;
SELECT /*+ ordered */ J.licence, C.nom FROM C, J WHERE J.cnum = C.cnum AND J.salaire > 10;
b) On remplace la table Joueur par une table plus grande 'BigJoueur'. On étudie la jointure par hachage entre les tables C et BigJoueur.
Observer que la jointure dans l'ordre C, BigJoueur
a un coût plus petit que celle dans l'ordre BigJoueur, C
SELECT /*+ ordered */ * FROM C, BigJoueur j WHERE j.cnum = c.cnum;
SELECT /*+ ordered */ * FROM BigJoueur j, C WHERE j.cnum = c.cnum;
Le coût total de la jointure est la somme du coûts de lecture des tables plus le coût de constuire la HashMap (cf. colonne TmpSpc). En déduire le coût de construction de la HashMap.
Enlever maintenant la directive ordered
et observer que le SGBD choisit de lire C avant BigJoueur car cela est moins coûteux.
c) Observer que lorsque le prédicat de sélection sur le salaire n'est pas très sélectif, le plan est toujours le même. Répondre en remplaçant sal>10 par sal>100, …, sal>1000.
d) On étudie maintenant le cas où le prédicat de sélection est très sélectif. Expliquer le plan de la requête suivante. Détailler comment chaque valeur de la colonne Cost est calculée.
SELECT J.licence, C.nom FROM C, j WHERE J.cnum = C.cnum AND salaire < 10050;
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 50 | 1300 | 60 (2)| 00:00:01 | |* 1 | HASH JOIN | | 50 | 1300 | 60 (2)| 00:00:01 | | 2 | TABLE ACCESS BY INDEX ROWID| J | 50 | 700 | 52 (0)| 00:00:01 | |* 3 | INDEX RANGE SCAN | I_J_SALAIRE | 50 | | 2 (0)| 00:00:01 | | 4 | TABLE ACCESS FULL | C | 5000 | 60000 | 7 (0)| 00:00:01 | -------------------------------------------------------------------------------------------- Predicate Information: 1 - access("J"."CNUM"="C"."CNUM") 3 - access("SALAIRE"<10050)
a) Expliquer le plan de la requête R2 suivante. Détailler comment chaque valeur de la colonne Cost est calculée.
SELECT J.licence, C.nom FROM C, j WHERE J.cnum = C.cnum AND salaire < 10006;
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 6 | 156 | 14 (0)| 00:00:01 | | 1 | NESTED LOOPS | | | | | | | 2 | NESTED LOOPS | | 6 | 156 | 14 (0)| 00:00:01 | | 3 | TABLE ACCESS BY INDEX ROWID| J | 6 | 84 | 8 (0)| 00:00:01 | |* 4 | INDEX RANGE SCAN | I_J_SALAIRE | 6 | | 2 (0)| 00:00:01 | |* 5 | INDEX UNIQUE SCAN | I_C_CNUM | 1 | | 0 (0)| 00:00:01 | | 6 | TABLE ACCESS BY INDEX ROWID | C | 1 | 12 | 1 (0)| 00:00:01 | --------------------------------------------------------------------------------------------- Predicate Information : 4 - access("SALAIRE"<10006) 5 - access("J"."CNUM"="C"."CNUM")
b) On veut utiliser l'index sur l'attribut de jointure cnum pour évaluer R1. Pour cela, on utilise une jointure par boucles imbriquées. La directive USE_NL
indique que la jointure doit être traitée par boucles imbriquées.
SELECT /*+ USE_NL(J,C) */ J.licence, C.nom FROM J, C WHERE J.cnum = C.cnum AND J.salaire > 10;
Dessiner l'arbre obtenu. Quelle table est parcourue séquentiellement une seule fois ? L'index choisi sur cnum est-il celui de la table C ou J ?
Quel est le coût de ce plan ? Expliquer comment il est obtenu à partir des feuilles. Pourquoi est-il très supérieur au coût de la jointure par hachage pour laquelle on lit J et C une seule fois?
Inversement, on évalue R1 en utilisant l'index sur cnum de la table J en fixant l'ordre (on impose d'itérer sur C avant J).
SELECT /*+ USE_NL(J,C) ordered*/ J.licence, C.nom FROM C,J WHERE J.cnum = C.cnum AND J.salaire > 10;
Dessiner l'arbre obtenu. Expliquer pourquoi le coût est plus grand. Ce plan fait combien d'accès à l'index ?
c) Jointure et sélection avec/sans index
Comparer les plans obtenus pour R2 avec ou sans index
SELECT /*+ no_index(J I_J_salaire) */ J.licence, C.nom FROM J, C WHERE J.cnum = C.cnum AND J.salaire <10006;
Soit la requête R3 :
SELECT /*+ use_nl(J,C,F) */ c.nom, f.budget FROM J, C, F WHERE J.cnum = C.cnum AND C.cnum = F.cnum AND c.division=1 AND J.salaire > 59000 AND j.sport = 'sport1';
La directive USE_NL permet d'imposer des jointures par boucles imbriquées afin d'utiliser les index sur l'attribut de jointure cnum de C et J et F.
On sait (cf. cours) qu'il y a 6 ordres de jointure possibles pour évaluer cette requête.
a) Compléter le tableau des ordres possibles :
ordre | coût |
---|---|
J, C, F | 88 |
… | … |
F, J, C | … |
D'après les résultats du tableau, dessiner l'arbre correspondant à l'ordre de moindre coût. Quels index sont utilisés ?
Pourquoi l'ordre J, F, C n'utilise pas l'index sur F.cnum? Observer que le SGBD propose de faire un produit cartésien entre J et F au lieu d'une jointure. Remplacer le prédicat de jointure J.cnum = C.cnum par le prédicat équivalent J.cnum = F.cnum, déduit par transitivité, et observer que cela permet d'utiliser l'index sur F.cnum pour la jointure de J avec F. Que peut-on conclure concernant la capacité du SGBD à explorer tous les ordres de jointure équivalents ?
b) Modifier R3 en une requête R4 de jointure et de sélection avec index : modifier seulement la valeur du salaire (59000) pour que le plan obtenu choisisse l'index sur le salaire. Quel serait le coût de R4 sans utiliser l'index sur le salaire ?
c) On force les index sur les attributs “J.salaire” et “C.division” pour évaluer R3
SELECT /*+ use_nl(J,C,F) index(C I_C_division) index(J I_J_salaire)*/ c.nom, f.budget FROM J, C, F WHERE J.cnum = C.cnum AND C.cnum = F.cnum AND c.division=1 AND J.salaire > 59000 AND j.sport = 'sport1';
Dessiner le plan obtenu. Quel est l'inconvénient d'utiliser l'index sur l'attribut division ?
a) Soit R4:
SELECT /*+ use_nl(J,C,F) */ C.nom, F.budget, J.licence FROM J, C, F WHERE J.cnum = C.cnum AND C.cnum = F.cnum ;
Dessiner l'arbre obtenu. Justifier l'odre choisit. Pourquoi lire F en premier et J en dernier ?
b) Compléter la clause WHERE de R4 pour obtenir R5 de telle sorte que l'ordre choisi par le SGBD soit C,F,J
c) Ajouter une 4ème table Z et proposer une requête de jointure entre les 4 tables de telle sorte que le plan de moindre coût ne soit pas un arbre linéaire à gauche. C'est à dire que le coût de P1 soit inférieur à P2 avec :
Le plan
| 1 | NESTED LOOPS |* 2 | TABLE ACCESS FULL | J | |* 3 | TABLE ACCESS BY INDEX ROWID | C | |* 4 | INDEX UNIQUE SCAN | I_C_CNUM |
est indentique au plan
| 1 | NESTED LOOPS | 2 | NESTED LOOPS |* 3 | TABLE ACCESS FULL | J | |* 4 | INDEX UNIQUE SCAN | I_C_CNUM | | 5 | TABLE ACCESS BY INDEX ROWID | C |
Dans les deux plans, la jointure est la même. Lire Joueur pour itérer: pour chaque n-uplet de Joueur, accéder à l'index sur (C.cnum) pour connaitre l'identifiant du nuplet du Club. Puis accéder à la table Club pour lire le n-uplet correspondant. (pour plus d'explications voir Lien
Documentation sur l'optimiseur d'Oracle
Aller vers BDR