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