| 1 |
<!-- |
|---|
| 2 |
$Header: /var/lib/cvs/pgsql-fr/sgml/arch-dev.sgml,v 1.13 2005/07/15 06:14:19 guillaume Exp $ |
|---|
| 3 |
--> |
|---|
| 4 |
|
|---|
| 5 |
<chapter id="overview"> |
|---|
| 6 |
<title>Présentation des mécanismes internes de PostgreSQL</title> |
|---|
| 7 |
|
|---|
| 8 |
<note> |
|---|
| 9 |
<title>Auteur</title> |
|---|
| 10 |
<para> |
|---|
| 11 |
Ce chapitre vient originellement de <xref linkend="SIM98">, la thèse de |
|---|
| 12 |
Stefan Simkovics préparée à l'université de technologie de Vienne sous la |
|---|
| 13 |
direction du Dr. Georg Gottlob et de son assistante Katrin Seyr. |
|---|
| 14 |
</para> |
|---|
| 15 |
</note> |
|---|
| 16 |
|
|---|
| 17 |
<para> |
|---|
| 18 |
Ce chapitre présente la structure interne du serveur |
|---|
| 19 |
<productname>PostgreSQL</productname>. Après avoir lu les sections suivantes, |
|---|
| 20 |
vous devriez avoir une idée sur la façon dont une requête est exécutée. Ce |
|---|
| 21 |
chapitre n'a pas pour but de donner une description détaillée des opérations |
|---|
| 22 |
internes de <productname>PostgreSQL</productname> car un tel document |
|---|
| 23 |
serait énorme. À la place, ce chapitre a pour but d'aider le lecteur à |
|---|
| 24 |
comprendre la suite générale des opérations arrivant au niveau serveur à |
|---|
| 25 |
partir du moment où une requête est reçue jusqu'au moment où les résultats |
|---|
| 26 |
sont renvoyés au client. |
|---|
| 27 |
</para> |
|---|
| 28 |
|
|---|
| 29 |
<sect1 id="query-path"> |
|---|
| 30 |
<title>Chemin d'une requête</title> |
|---|
| 31 |
|
|---|
| 32 |
<para> |
|---|
| 33 |
Ici, nous allons donner un rapide aperçu des étapes qu'une requête doit |
|---|
| 34 |
franchir pour obtenir un résultat. |
|---|
| 35 |
</para> |
|---|
| 36 |
|
|---|
| 37 |
<procedure> |
|---|
| 38 |
<step> |
|---|
| 39 |
<para> |
|---|
| 40 |
Une connexion doit être établie à partir d'une application au serveur |
|---|
| 41 |
<productname>PostgreSQL</productname>. L'application transmet une requête |
|---|
| 42 |
au serveur et attend de recevoir les résultats renvoyés par le serveur. |
|---|
| 43 |
</para> |
|---|
| 44 |
</step> |
|---|
| 45 |
|
|---|
| 46 |
<step> |
|---|
| 47 |
<para> |
|---|
| 48 |
L'<firstterm>étape de l'analyse</firstterm> vérifie la requête transmise |
|---|
| 49 |
par l'application au niveau de la syntaxe et crée un <firstterm>arbre |
|---|
| 50 |
représentant la requête</firstterm>. |
|---|
| 51 |
</para> |
|---|
| 52 |
</step> |
|---|
| 53 |
|
|---|
| 54 |
<step> |
|---|
| 55 |
<para> |
|---|
| 56 |
Le <firstterm>système de réécriture</firstterm> prend l'arbre représentant |
|---|
| 57 |
la requête et cherche les <firstterm>règles</firstterm> (stockées dans les |
|---|
| 58 |
<firstterm>catalogues système</firstterm>) à appliquer à l'arbre de la |
|---|
| 59 |
requête. Il exécute les transformations données dans les <firstterm>corps |
|---|
| 60 |
des règles</firstterm>. |
|---|
| 61 |
</para> |
|---|
| 62 |
|
|---|
| 63 |
<para> |
|---|
| 64 |
Une application du système de réécriture est la réalisation des |
|---|
| 65 |
<firstterm>vues</firstterm>. À chaque fois qu'une requête comprenant une |
|---|
| 66 |
vue (c'est-à-dire une <firstterm>table virtuelle</firstterm>) est exécutée, |
|---|
| 67 |
le système de réécriture modifie la requête de l'utilisateur par la |
|---|
| 68 |
<firstterm>définition de la vue</firstterm>, c'est-à-dire une requête qui |
|---|
| 69 |
accède aux <firstterm>tables de base</firstterm>. |
|---|
| 70 |
</para> |
|---|
| 71 |
</step> |
|---|
| 72 |
|
|---|
| 73 |
<step> |
|---|
| 74 |
<para> |
|---|
| 75 |
Le <firstterm>planificateur/optimiseur</firstterm> prend l'arbre |
|---|
| 76 |
(réécrit) de la requête et crée un <firstterm>plan |
|---|
| 77 |
d'exécution</firstterm> qui sera l'entrée de |
|---|
| 78 |
l'<firstterm>exécuteur</firstterm>. |
|---|
| 79 |
</para> |
|---|
| 80 |
|
|---|
| 81 |
<para> |
|---|
| 82 |
Il le fait tout d'abord en créant tous les <firstterm>chemins</firstterm> |
|---|
| 83 |
possibles menant au même résultat. Par exemple, s'il existe un index sur |
|---|
| 84 |
une relation à parcourir, il existe deux chemins pour le parcours. Une |
|---|
| 85 |
possibilité est un simple parcours séquentiel et l'autre possibilité est |
|---|
| 86 |
d'utiliser l'index. Ensuite, le coût pour l'exécution de chaque chemin est |
|---|
| 87 |
estimé et le chemin le moins coûteux est choisi. Ce dernier est étendu en |
|---|
| 88 |
un plan complet que l'exécuteur peut utiliser. |
|---|
| 89 |
</para> |
|---|
| 90 |
</step> |
|---|
| 91 |
|
|---|
| 92 |
<step> |
|---|
| 93 |
<para> |
|---|
| 94 |
L'exécuteur passe récursivement dans l'<firstterm>arbre constitué par le |
|---|
| 95 |
plan</firstterm> et retrouve les lignes suivant la façon |
|---|
| 96 |
représentée par le plan. L'exécuteur fait usage du <firstterm>système de |
|---|
| 97 |
stockage</firstterm> lors du parcours des relations, exécute les |
|---|
| 98 |
<firstterm>tris</firstterm> et <firstterm>jointures</firstterm>, évalue |
|---|
| 99 |
les <firstterm>qualifications</firstterm> et finalement renvoie les |
|---|
| 100 |
lignes en question. |
|---|
| 101 |
</para> |
|---|
| 102 |
</step> |
|---|
| 103 |
</procedure> |
|---|
| 104 |
|
|---|
| 105 |
<para> |
|---|
| 106 |
Dans les sections suivantes, nous allons parler de chacun des éléments |
|---|
| 107 |
discutés ci-dessus avec plus de détails pour permettre une meilleure |
|---|
| 108 |
compréhension des structures de données et de contrôle internes de |
|---|
| 109 |
<productname>PostgreSQL</productname>. |
|---|
| 110 |
</para> |
|---|
| 111 |
</sect1> |
|---|
| 112 |
|
|---|
| 113 |
<sect1 id="connect-estab"> |
|---|
| 114 |
<title>Moyens pour établir des connexions</title> |
|---|
| 115 |
|
|---|
| 116 |
<para> |
|---|
| 117 |
<productname>PostgreSQL</productname> est implémenté suivant un modèle |
|---|
| 118 |
client/serveur pour chaque <quote>processus par utilisateur</>. Dans ce |
|---|
| 119 |
modèle, il existe un <firstterm>processus client</firstterm> connecté à un |
|---|
| 120 |
seul <firstterm>processus serveur</firstterm>. Comme nous ne savons pas par |
|---|
| 121 |
avance combien de connexions seront établies, nous devons utiliser un |
|---|
| 122 |
<firstterm>processus maître</firstterm> qui lancera un processus serveur à |
|---|
| 123 |
chaque fois qu'une connexion sera demandée. Ce processus maître s'appelle |
|---|
| 124 |
<literal>postmaster</literal> et écoute sur le port TCP/IP spécifié les |
|---|
| 125 |
connexions entrantes. À chaque fois qu'une demande pour une connexion est |
|---|
| 126 |
détectée, le processus <literal>postmaster</literal> lance un nouveau |
|---|
| 127 |
processus fils appelé <literal>postgres</literal>. Les tâches du serveur |
|---|
| 128 |
(processus <literal>postgres</literal>) communiquent entre elles en |
|---|
| 129 |
utilisant des <firstterm>sémaphores</firstterm> et de la <firstterm>mémoire |
|---|
| 130 |
partagée</firstterm> pour s'assurer de l'intégrité des données lors d'un |
|---|
| 131 |
accès simultané aux données. |
|---|
| 132 |
</para> |
|---|
| 133 |
|
|---|
| 134 |
<para> |
|---|
| 135 |
Le processus client peut être tout programme comprenant le protocole |
|---|
| 136 |
<productname>PostgreSQL</productname> décrit dans le |
|---|
| 137 |
<xref linkend="protocol">. Beaucoup de clients sont basés sur la |
|---|
| 138 |
bibliothèque C <application>libpq</> mais plusieurs implémentations |
|---|
| 139 |
indépendantes du protocole existent, telle que le pilote Java |
|---|
| 140 |
<application>JDBC</>. |
|---|
| 141 |
</para> |
|---|
| 142 |
|
|---|
| 143 |
<para> |
|---|
| 144 |
Une fois la connexion établie, le processus client peut envoyer une requête |
|---|
| 145 |
au serveur (<firstterm>backend</firstterm>). La requête est transmise en |
|---|
| 146 |
texte simple, c'est-à-dire qu'aucune analyse n'a besoin d'être réalisée au |
|---|
| 147 |
niveau de l'<firstterm>interface</firstterm> (client). Le serveur analyse |
|---|
| 148 |
la requête, crée un <firstterm>plan d'exécution</firstterm>, exécute le |
|---|
| 149 |
plan et renvoie les lignes trouvées au client par la connexion établie. |
|---|
| 150 |
</para> |
|---|
| 151 |
</sect1> |
|---|
| 152 |
|
|---|
| 153 |
<sect1 id="parser-stage"> |
|---|
| 154 |
<title>Étape d'analyse</title> |
|---|
| 155 |
|
|---|
| 156 |
<para> |
|---|
| 157 |
L'<firstterm>étape d'analyse</firstterm> consiste en deux parties : |
|---|
| 158 |
|
|---|
| 159 |
<itemizedlist> |
|---|
| 160 |
<listitem> |
|---|
| 161 |
<para> |
|---|
| 162 |
L'<firstterm>analyseur</firstterm> défini dans |
|---|
| 163 |
<filename>gram.y</filename> et <filename>scan.l</filename> est construit |
|---|
| 164 |
en utilisant les outils Unix <application>yacc</application> et |
|---|
| 165 |
<application>lex</application>. |
|---|
| 166 |
</para> |
|---|
| 167 |
</listitem> |
|---|
| 168 |
<listitem> |
|---|
| 169 |
<para> |
|---|
| 170 |
Le <firstterm>processus de transformation</firstterm> fait des |
|---|
| 171 |
modifications et des ajouts aux structures de données renvoyées par |
|---|
| 172 |
l'analyseur. |
|---|
| 173 |
</para> |
|---|
| 174 |
</listitem> |
|---|
| 175 |
</itemizedlist> |
|---|
| 176 |
</para> |
|---|
| 177 |
|
|---|
| 178 |
<sect2> |
|---|
| 179 |
<title>Analyseur</title> |
|---|
| 180 |
|
|---|
| 181 |
<para> |
|---|
| 182 |
L'analyseur doit vérifier que la syntaxe de la chaîne de la requête |
|---|
| 183 |
(arrivant comme un texte ASCII) soit valide. Si la syntaxe est correcte, un |
|---|
| 184 |
<firstterm>arbre d'analyse</firstterm> est construit et renvoyé, sinon |
|---|
| 185 |
une erreur est retournée. Les analyseur et vérificateur syntaxiques sont |
|---|
| 186 |
implémentés en utilisant les outils Unix bien connus que sont |
|---|
| 187 |
<application>lex</application> et <application>yacc</application>. |
|---|
| 188 |
</para> |
|---|
| 189 |
|
|---|
| 190 |
<para> |
|---|
| 191 |
L'<firstterm>analyseur lexical</firstterm> est défini dans le fichier |
|---|
| 192 |
<filename>scan.l</filename> et est responsable de la reconnaissance des |
|---|
| 193 |
<firstterm>identificateurs</firstterm>, des <firstterm>mots clés |
|---|
| 194 |
SQL</firstterm>, etc. Pour chaque mot clé ou identificateur trouvé, un |
|---|
| 195 |
<firstterm>jeton</firstterm> est généré et renvoyé à l'analyseur. |
|---|
| 196 |
</para> |
|---|
| 197 |
|
|---|
| 198 |
<para> |
|---|
| 199 |
L'analyseur est défini dans le fichier <filename>gram.y</filename> et |
|---|
| 200 |
consiste en un ensemble de <firstterm>règles de grammaire</firstterm> et |
|---|
| 201 |
en des <firstterm>actions</firstterm> à exécuter lorsqu'une règle est |
|---|
| 202 |
découverte. Le code des actions (qui est en langage C) est utilisé pour |
|---|
| 203 |
construire l'arbre d'analyse. |
|---|
| 204 |
</para> |
|---|
| 205 |
|
|---|
| 206 |
<para> |
|---|
| 207 |
Le fichier <filename>scan.l</filename> est transformé en fichier source C |
|---|
| 208 |
<filename>scan.c</filename> en utilisant le programme |
|---|
| 209 |
<application>lex</application> et <filename>gram.y</filename> est |
|---|
| 210 |
transformé en <filename>gram.c</filename> en utilisant |
|---|
| 211 |
<application>yacc</application>. Après avoir réalisé ces transformations, |
|---|
| 212 |
un compilateur C normal peut être utilisé pour créer l'analyseur. Ne faites |
|---|
| 213 |
jamais de changement aux fichiers C générés car ils seront écrasés la |
|---|
| 214 |
prochaine fois que <application>lex</application> ou |
|---|
| 215 |
<application>yacc</application> seront appelés. |
|---|
| 216 |
|
|---|
| 217 |
<note> |
|---|
| 218 |
<para> |
|---|
| 219 |
Les transformations et compilations mentionnées sont normalement |
|---|
| 220 |
réalisées automatiquement en utilisant les |
|---|
| 221 |
<firstterm>makefile</firstterm> distribués avec les sources de |
|---|
| 222 |
<productname>PostgreSQL</productname>. |
|---|
| 223 |
</para> |
|---|
| 224 |
</note> |
|---|
| 225 |
</para> |
|---|
| 226 |
|
|---|
| 227 |
<para> |
|---|
| 228 |
Une description détaillée de <application>yacc</application> ou des règles |
|---|
| 229 |
de grammaire données dans <filename>gram.y</filename> serait en dehors du |
|---|
| 230 |
champ de ce document. Il existe de nombreux livres et documentations en |
|---|
| 231 |
relation avec <application>lex</application> et |
|---|
| 232 |
<application>yacc</application>. Vous devez être familier avec |
|---|
| 233 |
<application>yacc</application> avant de commencer à étudier la grammaire |
|---|
| 234 |
donnée dans <filename>gram.y</filename>, sinon vous ne comprendrez rien à |
|---|
| 235 |
ce qui s'y passe. |
|---|
| 236 |
</para> |
|---|
| 237 |
|
|---|
| 238 |
</sect2> |
|---|
| 239 |
|
|---|
| 240 |
<sect2> |
|---|
| 241 |
<title>Processus de transformation</title> |
|---|
| 242 |
|
|---|
| 243 |
<para> |
|---|
| 244 |
L'étape d'analyse crée un arbre d'analyse utilisant seulement les règles |
|---|
| 245 |
fixes de la structure syntaxique de SQL. Il ne fait aucune recherche dans |
|---|
| 246 |
les catalogues système, donc il n'y a aucune possibilité de comprendre |
|---|
| 247 |
la sémantique détaillée des opérations demandées. Après que l'analyseur |
|---|
| 248 |
ait fini, le <firstterm>processus de transformation</firstterm> prend |
|---|
| 249 |
l'arbre résultant de l'analyseur en entrée et réalise l'interprétation |
|---|
| 250 |
sémantique nécessaire pour connaître les tables, fonctions et opérateurs |
|---|
| 251 |
référencés par la requête. La structure de données construite pour |
|---|
| 252 |
représenter cette information est appelé l'<firstterm>arbre de la |
|---|
| 253 |
requête</>. |
|---|
| 254 |
</para> |
|---|
| 255 |
|
|---|
| 256 |
<para> |
|---|
| 257 |
La raison de la séparation de l'analyse brute et de l'analyse sémantique |
|---|
| 258 |
est que les recherches des catalogues système peuvent seulement se faire |
|---|
| 259 |
à l'intérieur d'une transaction, et nous ne voulons pas commencer une |
|---|
| 260 |
transaction immédiatement après avoir reçu une requête. L'analyse brute |
|---|
| 261 |
est suffisante pour identifier les commandes de contrôle des transactions |
|---|
| 262 |
(<command>BEGIN</>, <command>ROLLBACK</>, etc) et elles peuvent être |
|---|
| 263 |
correctement exécutées sans plus d'analyse. Une fois que nous savons que |
|---|
| 264 |
nous gérons une vraie requête (telle que <command>SELECT</> ou |
|---|
| 265 |
<command>UPDATE</>), nous pouvons commencer une transaction si nous n'y |
|---|
| 266 |
sommes pas déjà. C'est seulement maintenant que le processus de |
|---|
| 267 |
transformation peut être appelé. |
|---|
| 268 |
</para> |
|---|
| 269 |
|
|---|
| 270 |
<para> |
|---|
| 271 |
La plupart du temps, l'arbre d'une requête créé par le processus de |
|---|
| 272 |
transformation a une structure similaire à l'arbre d'analyse brute mais il |
|---|
| 273 |
existe beaucoup de différences dans le détail. Par exemple, un nœud |
|---|
| 274 |
<structname>FuncCall</> dans l'arbre d'analyse représente quelque chose qui |
|---|
| 275 |
ressemble syntaxiquement à l'appel d'une fonction. Il peut être transformé |
|---|
| 276 |
soit en nœud <structname>FuncExpr</> soit en nœud |
|---|
| 277 |
<structname>Aggref</> suivant que le nom référencé est une fonction |
|---|
| 278 |
ordinaire ou une fonction d'agrégat. De même, des informations sur les |
|---|
| 279 |
types de données réels des colonnes et des résultats sont ajoutées à |
|---|
| 280 |
l'arbre de la requête. |
|---|
| 281 |
</para> |
|---|
| 282 |
</sect2> |
|---|
| 283 |
</sect1> |
|---|
| 284 |
|
|---|
| 285 |
<sect1 id="rule-system"> |
|---|
| 286 |
<title>Système de règles de <productname>PostgreSQL</productname></title> |
|---|
| 287 |
|
|---|
| 288 |
<para> |
|---|
| 289 |
<productname>PostgreSQL</productname> supporte un puissant |
|---|
| 290 |
<firstterm>système de règles</firstterm> pour la spécification des |
|---|
| 291 |
<firstterm>vues</firstterm> et des <firstterm>mises à jour de |
|---|
| 292 |
vues</firstterm> ambiguës. À l'origine, le système de règles de |
|---|
| 293 |
<productname>PostgreSQL</productname> consistait en deux implémentations : |
|---|
| 294 |
|
|---|
| 295 |
<itemizedlist> |
|---|
| 296 |
<listitem> |
|---|
| 297 |
<para> |
|---|
| 298 |
Le premier a fonctionné en utilisant le <firstterm>niveau des |
|---|
| 299 |
lignes</firstterm> et était implémenté profondément dans |
|---|
| 300 |
l'<firstterm>exécuteur</firstterm>. Le système de règles était appelé |
|---|
| 301 |
à chaque fois qu'il fallait accéder à une ligne individuelle. Cette |
|---|
| 302 |
implémentation a été supprimée en 1995 quand la dernière version |
|---|
| 303 |
officielle du projet <productname>Berkeley Postgres</productname> a été |
|---|
| 304 |
transformé en <productname>Postgres95</productname>. |
|---|
| 305 |
</para> |
|---|
| 306 |
</listitem> |
|---|
| 307 |
|
|---|
| 308 |
<listitem> |
|---|
| 309 |
<para> |
|---|
| 310 |
La deuxième implémentation du système de règles est une technique appelée |
|---|
| 311 |
la <firstterm>réécriture de requêtes</firstterm>. Le <firstterm>système |
|---|
| 312 |
de réécriture</firstterm> est un module qui existe entre |
|---|
| 313 |
l'<firstterm>étape d'analyse</firstterm> et le |
|---|
| 314 |
<firstterm>planificateur/optimiseur</firstterm>. Cette technique est |
|---|
| 315 |
toujours implémentée. |
|---|
| 316 |
</para> |
|---|
| 317 |
</listitem> |
|---|
| 318 |
</itemizedlist> |
|---|
| 319 |
</para> |
|---|
| 320 |
|
|---|
| 321 |
<para> |
|---|
| 322 |
Le système de réécriture de requêtes est vu plus en détails dans le |
|---|
| 323 |
<xref linkend="rules">, donc il n'est pas nécessaire d'en parler ici. |
|---|
| 324 |
Nous indiquerons seulement qu'à la fois l'entrée et la sortie du système |
|---|
| 325 |
sont des arbres de requêtes, c'est-à-dire qu'il n'y a pas de changement |
|---|
| 326 |
dans la représentation ou le niveau du détail sémantique des arbres. La |
|---|
| 327 |
réécriture peut être imaginée comme une forme d'expansion de macro. |
|---|
| 328 |
</para> |
|---|
| 329 |
|
|---|
| 330 |
</sect1> |
|---|
| 331 |
|
|---|
| 332 |
<sect1 id="planner-optimizer"> |
|---|
| 333 |
<title>Planificateur/Optimiseur</title> |
|---|
| 334 |
|
|---|
| 335 |
<para> |
|---|
| 336 |
La tâche du <firstterm>planificateur/optimiseur</firstterm> est de créer un |
|---|
| 337 |
plan d'exécution optimal. En fait, une requête SQL donnée (et donc, l'arbre |
|---|
| 338 |
d'une requête) peut être exécutée de plusieurs façons, chacune arrivant |
|---|
| 339 |
au même résultat. Si ce calcul est possible, l'optimiseur de la requête |
|---|
| 340 |
examinera chacun des plans d'exécution possibles pour finir par |
|---|
| 341 |
sélectionner le plan d'exécution estimé comme étant le plus rapide. |
|---|
| 342 |
</para> |
|---|
| 343 |
|
|---|
| 344 |
<note> |
|---|
| 345 |
<para> |
|---|
| 346 |
Dans certaines situations, examiner chaque moyen d'exécuter une requête |
|---|
| 347 |
prendrait beaucoup de temps et de mémoire. En particulier, cela arrive lors |
|---|
| 348 |
de l'exécution de requêtes impliquant un grand nombre de jointures. Pour |
|---|
| 349 |
déterminer un plan de requête raisonnable (et non optimal) dans un temps |
|---|
| 350 |
raisonnable, <productname>PostgreSQL</productname> utilise un |
|---|
| 351 |
<xref linkend="geqo" endterm="geqo-title">. |
|---|
| 352 |
</para> |
|---|
| 353 |
</note> |
|---|
| 354 |
|
|---|
| 355 |
<para> |
|---|
| 356 |
La procédure de recherche du planificateur fonctionne en fait avec des |
|---|
| 357 |
structures de données appelés <firstterm>chemins</>, qui sont simplement des |
|---|
| 358 |
représentations minimales de plans contenant seulement l'information |
|---|
| 359 |
nécessaire pour que le planificateur puisse prendre ses décisions. Une fois |
|---|
| 360 |
le chemin le moins coûteux déterminé, un <firstterm>arbre plan</> est |
|---|
| 361 |
construit pour être donné à l'exécuteur. Ceci représente le plan d'exécution |
|---|
| 362 |
désiré avec suffisamment de détails pour que l'exécuteur puisse le lancer. |
|---|
| 363 |
Dans le reste de cette section, nous ignorerons la distinction entre chemins |
|---|
| 364 |
et plans. |
|---|
| 365 |
</para> |
|---|
| 366 |
|
|---|
| 367 |
<sect2> |
|---|
| 368 |
<title>Générer les plans possibles</title> |
|---|
| 369 |
|
|---|
| 370 |
<para> |
|---|
| 371 |
Le planificateur/optimiseur commence par générer des plans pour parcourir |
|---|
| 372 |
chaque relation (table) invididuelle utilisée dans la requête. Les plans |
|---|
| 373 |
possibles sont déterminés par les index disponibles pour chaque relation. |
|---|
| 374 |
Il est toujours possible de réaliser un parcours séquentiel sur une |
|---|
| 375 |
relation, donc un plan de parcours séquentiel est toujours créé. Supposons |
|---|
| 376 |
qu'un index soit défini sur une relation (par exemple un index B-tree) et |
|---|
| 377 |
qu'une requête contienne le filtre <literal>relation.attribut OPR |
|---|
| 378 |
constante</literal>. Si <literal>relation.attribut</literal> correspond à |
|---|
| 379 |
la clé de l'index B-tree et <literal>OPR</literal> est un des opérateurs |
|---|
| 380 |
listés dans la <firstterm>classe d'opérateurs</> de l'index, un autre plan |
|---|
| 381 |
est créé en utilisant l'index B-tree pour parcourir la relation. S'il |
|---|
| 382 |
existe d'autres index et que les restrictions de la requête font |
|---|
| 383 |
correspondre une clé à un index, d'autres plans seront considérés. |
|---|
| 384 |
</para> |
|---|
| 385 |
|
|---|
| 386 |
<para> |
|---|
| 387 |
Une fois que tous les plans probables ont été trouvés pour parcourir les |
|---|
| 388 |
relations simples, des plans pour les relations jointes sont créés. Le |
|---|
| 389 |
planificateur/optimiseur préfère considérer les jointures entre deux |
|---|
| 390 |
relations pour lesquelles il existe une clause de jointure correspondante |
|---|
| 391 |
dans la qualification <literal>WHERE</literal> (c'est-à-dire pour lequel |
|---|
| 392 |
une restriction comme <literal>where rel1.attr1=rel2.attr2</literal> |
|---|
| 393 |
existe). Des paires de jointures sans clause de jointure sont considérées |
|---|
| 394 |
seulement quand il n'existe pas d'autre choix, c'est-à-dire lorsqu'une |
|---|
| 395 |
relation particulière n'a pas de clause de jointure disponible vers toute |
|---|
| 396 |
autre relation. Tous les plans possibles sont générés pour chaque paire de |
|---|
| 397 |
jointure considérée par le planificateur/optimiseur. Les trois stratégies |
|---|
| 398 |
possibles de jointure sont : |
|---|
| 399 |
|
|---|
| 400 |
<itemizedlist> |
|---|
| 401 |
<listitem> |
|---|
| 402 |
<para> |
|---|
| 403 |
<firstterm>jointure de boucle imbriquée</firstterm> (NdT : nested |
|---|
| 404 |
loop join) : la relation de droite est parcourue une fois pour |
|---|
| 405 |
chaque ligne trouvée dans la relation de gauche. Cette stratégie est |
|---|
| 406 |
facile à implémenter mais peut être très coûteuse en temps. (Par contre, |
|---|
| 407 |
si la relation de droite peut être parcourue à l'aide d'un index, ceci |
|---|
| 408 |
peut être une bonne stratégie. Il est possible d'utiliser des valeurs |
|---|
| 409 |
provenant de la ligne actuelle de la relation de gauche comme clés pour |
|---|
| 410 |
un parcours indexé à droite.) |
|---|
| 411 |
</para> |
|---|
| 412 |
</listitem> |
|---|
| 413 |
|
|---|
| 414 |
<listitem> |
|---|
| 415 |
<para> |
|---|
| 416 |
<firstterm>jointure tri et assemblage</firstterm> (NdT : merge |
|---|
| 417 |
sort join) : Chaque relation est triée sur les attributs de la |
|---|
| 418 |
jointure avant que la jointure ne commence. Puis, les deux relations |
|---|
| 419 |
sont parcourues en parallèle et les lignes correspondantes sont |
|---|
| 420 |
combinées pour former des lignes jointes. Ce type de jointure est plus |
|---|
| 421 |
intéressant parce que chaque relation n'est parcourue qu'une seule fois. |
|---|
| 422 |
Le tri requis pourrait être réalisé soit par une étape explicite de tri |
|---|
| 423 |
soit en parcourant la relation dans le bon ordre en utilisant un index |
|---|
| 424 |
sur la clé de la jointure. |
|---|
| 425 |
</para> |
|---|
| 426 |
</listitem> |
|---|
| 427 |
|
|---|
| 428 |
<listitem> |
|---|
| 429 |
<para> |
|---|
| 430 |
<firstterm>jointure de découpage</firstterm> (hash join) : la |
|---|
| 431 |
relation de droite est tout d'abord parcourue et chargée dans une table |
|---|
| 432 |
de découpage en utilisant ses attributs de jointure comme clés de |
|---|
| 433 |
découpage. Ensuite, la relation de gauche est parcourue et les valeurs |
|---|
| 434 |
appropriées de chaque ligne trouvée sont utilisées comme clés de |
|---|
| 435 |
découpage pour localiser les lignes correspondantes dans la table. |
|---|
| 436 |
</para> |
|---|
| 437 |
</listitem> |
|---|
| 438 |
</itemizedlist> |
|---|
| 439 |
</para> |
|---|
| 440 |
|
|---|
| 441 |
<para> |
|---|
| 442 |
Quand la requête implique plus de deux relations, le résultat final doit |
|---|
| 443 |
être construit par un arbre d'étapes de jointure, chacune ayant deux |
|---|
| 444 |
entrées. Le planificateur examine les séquences de jointure possibles pour |
|---|
| 445 |
trouver le moins cher. |
|---|
| 446 |
</para> |
|---|
| 447 |
|
|---|
| 448 |
<para> |
|---|
| 449 |
L'arbre de plan terminé est composé de parcours séquentiels ou indexés des |
|---|
| 450 |
relations de base, plus les nœuds des jointures en boucle, jointures |
|---|
| 451 |
tri et rassemblement, et jointures de découpage si nécessaire, plus toutes |
|---|
| 452 |
les étapes auxiliaires nécessaires, tels que les nœuds de tri ou les |
|---|
| 453 |
nœuds de calcul des fonctions d'agrégat. La plupart des types de |
|---|
| 454 |
nœud de plan ont la capacité supplémentaire de faire une |
|---|
| 455 |
<firstterm>sélection</> (rejetant les lignes qui ne correspondent pas à une |
|---|
| 456 |
condition booléenne spécifiée) et une <firstterm>projection</> (calcul d'un |
|---|
| 457 |
ensemble de colonnes dérivées basé sur des valeurs de colonnes données, |
|---|
| 458 |
c'est-à-dire l'évaluation d'expressions scalaires si nécessaire). Une des |
|---|
| 459 |
responsabilités du planificateur est d'attacher les conditions de |
|---|
| 460 |
sélection à partir de la clause <literal>WHERE</literal> et du calcul des |
|---|
| 461 |
expressions de calculs requises aux nœuds les plus appropriés de |
|---|
| 462 |
l'arbre de plan. |
|---|
| 463 |
</para> |
|---|
| 464 |
</sect2> |
|---|
| 465 |
</sect1> |
|---|
| 466 |
|
|---|
| 467 |
<sect1 id="executor"> |
|---|
| 468 |
<title>Exécuteur</title> |
|---|
| 469 |
|
|---|
| 470 |
<para> |
|---|
| 471 |
L'<firstterm>exécuteur</firstterm> prend le plan envoyé par le |
|---|
| 472 |
planificateur/optimiseur et l'exécute récursivement pour extraire |
|---|
| 473 |
l'ensemble requis de lignes. Il s'agit principalement d'un mécanisme de |
|---|
| 474 |
pipeline en demande-envoi. Chaque fois qu'un nœud du plan est appelé, |
|---|
| 475 |
il doit apporter une ligne supplémentaire ou indiquer qu'il a fini d'envoyer |
|---|
| 476 |
des lignes. |
|---|
| 477 |
</para> |
|---|
| 478 |
|
|---|
| 479 |
<para> |
|---|
| 480 |
Pour donner un exemple concret, supposons que le nœud supérieur soit |
|---|
| 481 |
un nœud <literal>MergeJoin</literal>. Avant de pouvoir faire une |
|---|
| 482 |
fusion, deux lignes doivent être récupérées (une pour chaque sous-plan). |
|---|
| 483 |
L'exécuteur s'appelle donc récursivement pour exécuter les sous-plans (en |
|---|
| 484 |
commençant par le sous-plan attaché à l'<literal>arbre gauche</literal>). |
|---|
| 485 |
Le nouveau nœud supérieur (le nœud supérieur du sous-plan |
|---|
| 486 |
gauche) est, disons, un nœud <literal>Sort</literal> (NdT : Tri) |
|---|
| 487 |
et un appel récursif est une nouvelle fois nécessaire pour obtenir une ligne |
|---|
| 488 |
en entrée. Le nœud fils de <literal>Sort</literal> pourrait être un |
|---|
| 489 |
nœud <literal>SeqScan</>, représentant la lecture réelle d'une table. |
|---|
| 490 |
L'exécution de ce nœud fait que l'exécuteur récupère une ligne à |
|---|
| 491 |
partir de la table et la renvoie au nœud appelant. Le nœud |
|---|
| 492 |
<literal>Sort</literal> appellera de façon répétée son fils pour obtenir |
|---|
| 493 |
toutes les lignes à trier. Quand l'entrée est terminée (indiqué par le |
|---|
| 494 |
nœud fils renvoyant un NULL au lieu d'une ligne), le code de |
|---|
| 495 |
<literal>Sort</literal> est enfin capable de renvoyer sa première ligne en |
|---|
| 496 |
sortie, c'est-à-dire le premier suivant l'ordre de tri. Il conserve les |
|---|
| 497 |
lignes restantes en mémoire de façon à les renvoyer dans le bon ordre en |
|---|
| 498 |
réponse à des demandes ultérieures. |
|---|
| 499 |
</para> |
|---|
| 500 |
|
|---|
| 501 |
<para> |
|---|
| 502 |
Le nœud <literal>MergeJoin</literal> demande de la même façon la |
|---|
| 503 |
première ligne à partir du sous-plan droit. Ensuite, il compare les deux |
|---|
| 504 |
lignes pour savoir si elles peuvent être jointes ; si c'est le cas, il |
|---|
| 505 |
renvoie la ligne de jointure à son appelant. Au prochain appel, ou |
|---|
| 506 |
immédiatement s'il ne peut pas joindre la paire actuelle d'entrées, il |
|---|
| 507 |
avance sur la prochaine ligne d'une des deux tables (suivant le résultat de |
|---|
| 508 |
la comparaison), et vérifie de nouveau la correspondance. Finalement, un |
|---|
| 509 |
sous-plan est terminé et le nœud <literal>MergeJoin</literal> renvoie |
|---|
| 510 |
NULL pour indiquer qu'il n'y a plus de lignes jointes à former. |
|---|
| 511 |
</para> |
|---|
| 512 |
|
|---|
| 513 |
<para> |
|---|
| 514 |
Les requêtes complexes peuvent nécessiter un grand nombre de niveaux de |
|---|
| 515 |
nœuds pour les plans, mais l'approche générale est la même : |
|---|
| 516 |
chaque nœud est exécuté et renvoie sa prochaine ligne en sortie à |
|---|
| 517 |
chaque fois qu'il est appelé. Chaque nœud est responsable aussi de |
|---|
| 518 |
l'application de toute expression de sélection ou de projection qui lui ont |
|---|
| 519 |
été confiées par le planificateur. |
|---|
| 520 |
</para> |
|---|
| 521 |
|
|---|
| 522 |
<para> |
|---|
| 523 |
Le mécanisme de l'exécuteur est utilisé pour évaluer les quatre types de |
|---|
| 524 |
requêtes de base en SQL : <command>SELECT</>, <command>INSERT</>, |
|---|
| 525 |
<command>UPDATE</> et <command>DELETE</>. Pour <command>SELECT</>, le code |
|---|
| 526 |
de l'exécuteur de plus haut niveau a seulement besoin d'envoyer chaque ligne |
|---|
| 527 |
retournée par l'arbre plan de la requête vers le client. Pour |
|---|
| 528 |
<command>INSERT</>, chaque ligne renvoyée est insérée dans la table cible |
|---|
| 529 |
spécifiée par le <command>INSERT</>. (Une simple commande |
|---|
| 530 |
<command>INSERT ... VALUES</> crée un arbre plan trivial consistant en un |
|---|
| 531 |
seul nœud, <literal>Result</>, calculant une seule ligne de résultat. |
|---|
| 532 |
Mais <command>INSERT ... SELECT</> peut demander la pleine puissance du |
|---|
| 533 |
mécanisme de l'exécuteur.) Pour <command>UPDATE</>, le planificateur |
|---|
| 534 |
s'arrange pour que chaque ligne calculée inclue toutes les valeurs mises à |
|---|
| 535 |
jour des colonnes, plus le <firstterm>TID</> (tuple ID ou l'identifiant de |
|---|
| 536 |
la ligne) de la ligne de la cible originale ; l'exécuteur de plus haut |
|---|
| 537 |
niveau utilise cette information pour créer une nouvelle ligne mise à jour |
|---|
| 538 |
et pour marquer la suppression de l'ancienne ligne. Pour <command>DELETE</>, |
|---|
| 539 |
la seule colonne renvoyée par le plan est le TID, et l'exécuteur de plus |
|---|
| 540 |
haut niveau utilise simplement le TID pour visiter chaque ligne cible et la |
|---|
| 541 |
marquer comme supprimée. |
|---|
| 542 |
</para> |
|---|
| 543 |
|
|---|
| 544 |
</sect1> |
|---|
| 545 |
|
|---|
| 546 |
</chapter> |
|---|
| 547 |
|
|---|
| 548 |
<!-- Keep this comment at the end of the file |
|---|
| 549 |
Local variables: |
|---|
| 550 |
mode:sgml |
|---|
| 551 |
sgml-omittag:nil |
|---|
| 552 |
sgml-shorttag:t |
|---|
| 553 |
sgml-minimize-attributes:nil |
|---|
| 554 |
sgml-always-quote-attributes:t |
|---|
| 555 |
sgml-indent-step:1 |
|---|
| 556 |
sgml-indent-data:t |
|---|
| 557 |
sgml-parent-document:nil |
|---|
| 558 |
sgml-default-dtd-file:"./reference.ced" |
|---|
| 559 |
sgml-exposed-tags:nil |
|---|
| 560 |
sgml-local-catalogs:("/usr/lib/sgml/catalog") |
|---|
| 561 |
sgml-local-ecat-files:nil |
|---|
| 562 |
End: |
|---|
| 563 |
--> |
|---|