forked from gleu/pgdocs_fr
/
planstats.xml
453 lines (372 loc) · 19.2 KB
/
planstats.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
<?xml version="1.0" encoding="ISO-8859-15"?>
<!-- Dernière modification
le $Date$
par $Author$
révision $Revision$ -->
<chapter id="planner-stats-details">
<title>Comment le planificateur utilise les statistiques</title>
<para>
Ce chapitre est construit sur les informations fournies dans <xref
linkend="using-explain"/> et <xref linkend="planner-stats"/> pour montrer
certains détails supplémentaires sur la façon dont le planificateur
utilise les statistiques système pour estimer le nombre de lignes que chaque
partie d'une requête pourrait renvoyer. C'est une partie
importante du processus de planification, fournissant une
bonne partie des informations pour le calcul des coûts.
</para>
<para>
Le but de ce chapitre n'est pas de documenter le code en détail mais plutôt
de présenter un aperçu du
fonctionnement. Ceci aidera peut-être la phase d'apprentissage pour quelqu'un
souhaitant lire le code.
</para>
<sect1 id="row-estimation-examples">
<title>Exemples d'estimation des lignes</title>
<indexterm zone="row-estimation-examples">
<primary>estimation de lignes</primary>
<secondary>planificateurr</secondary>
</indexterm>
<para>
Les exemples montrés ci-dessous utilisent les tables de la base de tests de
régression de <productname>PostgreSQL</productname>. Les affichages indiqués
sont pris depuis la version 8.3. Le comportement des versions précédentes
(ou ultérieures) pourraient varier. Notez aussi que, comme
<command>ANALYZE</command> utilise un échantillonage statistique lors de
la réalisation des statistiques, les résultats peuvent changer légèrement
après toute exécution d'<command>ANALYZE</command>.
</para>
<para>
Commençons avec une requête simple :
<programlisting>EXPLAIN SELECT * FROM tenk1;
QUERY PLAN
-------------------------------------------------------------
Seq Scan on tenk1 (cost=0.00..458.00 rows=10000 width=244)
</programlisting>
Comment le planificateur détermine la cardinalité de
<structname>tenk1</structname> est couvert dans <xref
linkend="planner-stats"/> mais est répété ici pour être complet. Le nombre de
pages et de lignes est trouvé dans <structname>pg_class</structname> :
<programlisting>SELECT relpages, reltuples FROM pg_class WHERE relname = 'tenk1';
relpages | reltuples
----------+-----------
358 | 10000
</programlisting>
Ces nombres sont corrects à partir du dernier <command>VACUUM</command> ou
<command>ANALYZE</command> sur la table. Le planificateur récupère
ensuite le nombre de pages actuel dans la table (c'est une opération peu
coûteuse, ne nécessitant pas un parcours de table). Si c'est différent de
<structfield>relpages</structfield>, alors
<structfield>reltuples</structfield> est modifié en accord pour arriver à
une estimation actuelle du nombre de lignes. Dans ce cas, les valeurs sont
correctes donc l'estimation du nombre de lignes est identique à
<structfield>reltuples</structfield>.
</para>
<para>
Passons à un exemple avec une condition dans sa clause
<literal>WHERE</literal> :
<programlisting>EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 1000;
QUERY PLAN
--------------------------------------------------------------------------------
Bitmap Heap Scan on tenk1 (cost=24.06..394.64 rows=1007 width=244)
Recheck Cond: (unique1 < 1000)
-> Bitmap Index Scan on tenk1_unique1 (cost=0.00..23.80 rows=1007 width=0)
Index Cond: (unique1 < 1000)
</programlisting>
Le planificateur examine la condition de la clause <literal>WHERE</literal>
et cherche la fonction de sélectivité à partir de l'opérateur
<literal><</literal> dans <structname>pg_operator</structname>. C'est
contenu dans la colonne <structfield>oprrest</structfield> et le résultat,
dans ce cas, est <function>scalarltsel</function>. La fonction
<function>scalarltsel</function> récupère l'histogramme pour
<structfield>unique1</structfield> à partir de
<classname>pg_statistics</classname>. Pour les requêtes manuelles, il est
plus simple de regarder dans la vue <structname>pg_stats</structname> :
<programlisting>SELECT histogram_bounds FROM pg_stats
WHERE tablename='tenk1' AND attname='unique1';
histogram_bounds
------------------------------------------------------
{0,993,1997,3050,4040,5036,5957,7057,8029,9016,9995}
</programlisting>
Ensuite, la fraction de l'histogramme occupée par <quote>< 1000</quote>
est traitée. C'est la sélectivité. L'histogramme divise l'ensemble en plus
petites parties d'égales fréquences, donc tout ce que nous devons faire est
de localiser la partie où se trouve notre valeur et compter une
<emphasis>partie</emphasis> d'elle et <emphasis>toutes</emphasis> celles
qui la précèdent. La valeur 1000 est clairement dans la seconde partie
(993-1997), donc en supposant une distribution linéaire des valeurs à
l'intérieur de chaque partie, nous pouvons calculer la sélectivité comme
étant :
<programlisting>selectivity = (1 + (1000 - bucket[2].min)/(bucket[2].max - bucket[2].min))/num_buckets
= (1 + (1000 - 993)/(1997 - 993))/10
= 0.100697
</programlisting>
c'est-à-dire une partie complète plus une fraction linéaire de la seconde,
divisée par le nombre de parties. Le nombre de lignes estimées peut
maintenant être calculé comme le produit de la sélectivité et de la
cardinalité de <structname>tenk1</structname> :
<programlisting>rows = rel_cardinality * selectivity
= 10000 * 0.100697
= 1007 (rounding off)
</programlisting>
</para>
<para>
Maintenant, considérons un exemple avec une condition d'égalité dans sa
clause <literal>WHERE</literal> :
<programlisting>EXPLAIN SELECT * FROM tenk1 WHERE stringu1 = 'CRAAAA';
QUERY PLAN
----------------------------------------------------------
Seq Scan on tenk1 (cost=0.00..483.00 rows=30 width=244)
Filter: (stringu1 = 'CRAAAA'::name)
</programlisting>
De nouveau, le planificateur examine la condition de la clause
<literal>WHERE</literal>
et cherche la fonction de sélectivité pour <literal>=</literal>, qui est
<function>eqsel</function>. Pour une estimation d'égalité, l'histogramme
n'est pas utile ; à la place, la liste des valeurs les plus communes
(<firstterm>most common values</firstterm>, d'où l'acronyme
<acronym>MCV</acronym> fréquemment utilisé) est utilisé pour déterminer la
sélectivité. Regardons-les avec quelques colonnes
supplémentaires qui nous serons utiles plus tard :
<programlisting>SELECT null_frac, n_distinct, most_common_vals, most_common_freqs FROM pg_stats
WHERE tablename='tenk1' AND attname='stringu1';
null_frac | 0
n_distinct | 676
most_common_vals | {EJAAAA,BBAAAA,CRAAAA,FCAAAA,FEAAAA,GSAAAA,JOAAAA,MCAAAA,NAAAAA,WGAAAA}
most_common_freqs | {0.00333333,0.003,0.003,0.003,0.003,0.003,0.003,0.003,0.003,0.003}
</programlisting>
Comme <literal>CRAAAA</literal> apparaît dans la liste des MCV, la
sélectivité est tout simplement l'entrée correspondante dans la liste des
fréquences les plus courantes (<acronym>MCF</acronym>, acronyme de
<foreignphrase>Most Common Frequencies</foreignphrase>) :
<programlisting>selectivity = mcf[3]
= 0.003
</programlisting>
Comme auparavant, le nombre estimé de lignes est seulement le produit de ceci avec la
cardinalité de <structname>tenk1</structname> comme précédemment :
<programlisting>rows = 10000 * 0.003
= 30
</programlisting>
</para>
<para>
Maintenant, considérez la même requête mais avec une constante qui n'est pas
dans la liste <acronym>MCV</acronym> :
<programlisting>EXPLAIN SELECT * FROM tenk1 WHERE stringu1 = 'xxx';
QUERY PLAN
----------------------------------------------------------
Seq Scan on tenk1 (cost=0.00..483.00 rows=15 width=244)
Filter: (stringu1 = 'xxx'::name)
</programlisting>
C'est un problème assez différent, comment estimer la sélectivité quand la
valeur n'est <emphasis>pas</emphasis> dans la liste <acronym>MCV</acronym>.
L'approche est d'utiliser le fait que la valeur n'est pas dans la liste,
combinée avec la connaissance des fréquences pour tout les
<acronym>MCV</acronym> :
<programlisting>
selectivity = (1 - sum(mvf))/(num_distinct - num_mcv)
= (1 - (0.00333333 + 0.003 + 0.003 + 0.003 + 0.003 + 0.003 +
0.003 + 0.003 + 0.003 + 0.003))/(676 - 10)
= 0.0014559
</programlisting>
C'est-à-dire ajouter toutes les fréquences pour les <acronym>MCV</acronym>
et les soustraire d'un, puis les diviser par le nombre des
<emphasis>autres</emphasis> valeurs distinctes. Notez qu'il n'y a pas de
valeurs NULL, donc vous n'avez pas à vous en inquiéter (sinon nous pourrions
soustraire la fraction NULL à partir du numérateur). Le nombre estimé de
lignes est ensuite calculé comme d'habitude :
<programlisting>
rows = 10000 * 0.0014559
= 15 (rounding off)
</programlisting>
</para>
<para>
L'exemple précédent avec <literal>unique1 < 1000</literal> était une
sur-simplification de ce que <function>scalarltsel</function> faisait
réellement ; maintenant que nous avons vu un exemple de l'utilisation
des MCV, nous pouvons ajouter quelques détails supplémentaires. L'exemple
était correct aussi loin qu'il a été car, comme
<structfield>unique1</structfield> est une colonne unique, elle n'a pas de
MCV (évidemment, n'avoir aucune valeur n'est pas plus courant que toute
autre valeur). Pour une colonne non unique, il y a normalement un histogramme
et une liste MCV, et <emphasis>l'histogramme n'inclut pas la portion de la
population de colonne représentée par les MCV</emphasis>. Nous le faisons
ainsi parce que cela permet une estimation plus précise. Dans cette
situation, <function>scalarltsel</function> s'applique directement à la
condition (c'est-à-dire <quote>< 1000</quote>) pour chaque valeur de la
liste MCV, et ajoute les fréquence des MCV pour lesquelles la condition
est vérifiée. Ceci donne une estimation exacte de la sélectivité dans la
portion de la table qui est MCV. L'histogramme est ensuite utilisée de la
même façon que ci-dessus pour estimer la sélectivité dans la portion de la
table qui n'est pas MCV, et ensuite les deux nombres sont combinées pour
estimer la sélectivité. Par exemple, considérez
<programlisting>
EXPLAIN SELECT * FROM tenk1 WHERE stringu1 < 'IAAAAA';
QUERY PLAN
------------------------------------------------------------
Seq Scan on tenk1 (cost=0.00..483.00 rows=3077 width=244)
Filter: (stringu1 < 'IAAAAA'::name)
</programlisting>
Nous voyons déjà l'information MCV pour <structfield>stringu1</structfield>,
et voici son histogramme :
<programlisting>
SELECT histogram_bounds FROM pg_stats
WHERE tablename='tenk1' AND attname='stringu1';
histogram_bounds
--------------------------------------------------------------------------------
{AAAAAA,CQAAAA,FRAAAA,IBAAAA,KRAAAA,NFAAAA,PSAAAA,SGAAAA,VAAAAA,XLAAAA,ZZAAAA}
</programlisting>
En vérifiant la liste MCV, nous trouvons que la condition <literal>stringu1
< 'IAAAAA'</literal> est satisfaite par les six premières entrées et non
pas les quatre dernières, donc la sélectivité dans la partie MCV de la
population est :
<programlisting>
selectivity = sum(relevant mvfs)
= 0.00333333 + 0.003 + 0.003 + 0.003 + 0.003 + 0.003
= 0.01833333
</programlisting>
Additionner toutes les MFC nous indique aussi que la fraction totale de
la population représentée par les MCV est de 0.03033333, et du coup la
fraction représentée par l'histogramme est de 0.96966667 (encore une fois,
il n'y a pas de NULL, sinon nous devrions les exclure ici). Nous pouvons
voir que la valeur <literal>IAAAAA</literal> tombe près de la fin du
troisième jeton d'histogramme. En utilisant un peu de suggestions sur la
fréquence des caractères différents, le planificateur arrive à
l'estimation 0.298387 pour la portion de la population de l'histogramme
qui est moindre que <literal>IAAAAA</literal>. Ensuite nous combinons les
estimations pour les populations MCV et non MCV :
<programlisting>
selectivity = mcv_selectivity + histogram_selectivity * histogram_fraction
= 0.01833333 + 0.298387 * 0.96966667
= 0.307669
rows = 10000 * 0.307669
= 3077 (rounding off)
</programlisting>
Dans cet exemple particulier, la correction à partir de la liste MCV est
très petit car la distribution de la colonne est réellement assez plat
(les statistiques affichant ces valeurs particulières comme étant plus
communes que les autres sont principalement dûes à une erreur
d'échantillonage). Dans un cas plus typique où certaines valeurs sont
significativement plus communes que les autres, ce processus compliqué
donne une amélioration utile dans la précision car la sélectivité pour les
valeurs les plus communes est trouvée exactement.
</para>
<para>
Maintenant, considérons un cas avec plus d'une condition
dans la clause <literal>WHERE</literal> :
<programlisting>
EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 1000 AND stringu1 = 'xxx';
QUERY PLAN
--------------------------------------------------------------------------------
Bitmap Heap Scan on tenk1 (cost=23.80..396.91 rows=1 width=244)
Recheck Cond: (unique1 < 1000)
Filter: (stringu1 = 'xxx'::name)
-> Bitmap Index Scan on tenk1_unique1 (cost=0.00..23.80 rows=1007 width=0)
Index Cond: (unique1 < 1000)
</programlisting>
Le planificateur suppose que les deux conditions sont indépendantes, pour
que les sélectivités individuelles des clauses puissent être multipliées
ensemble :
<programlisting>
selectivity = selectivity(unique1 < 1000) * selectivity(stringu1 = 'xxx')
= 0.100697 * 0.0014559
= 0.0001466
rows = 10000 * 0.0001466
= 1 (rounding off)
</programlisting>
Notez que le nombre de lignes estimé être renvoyées à partir bitmap
index scan reflète seulement la condition utilisée avec l'index ; c'est
important car cela affecte l'estimation du coût pour les récupérations
suivantes sur la table.
</para>
<para>
Enfin, nous examinerons une requête qui implique une jointure :
<programlisting>
EXPLAIN SELECT * FROM tenk1 t1, tenk2 t2
WHERE t1.unique1 < 50 AND t1.unique2 = t2.unique2;
QUERY PLAN
-----------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------
Nested Loop (cost=4.64..456.23 rows=50 width=488)
-> Bitmap Heap Scan on tenk1 t1 (cost=4.64..142.17 rows=50 width=244)
Recheck Cond: (unique1 < 50)
-> Bitmap Index Scan on tenk1_unique1 (cost=0.00..4.63 rows=50 width=0)
Index Cond: (unique1 < 50)
-> Index Scan using tenk2_unique2 on tenk2 t2 (cost=0.00..6.27 rows=1 width=244)
Index Cond: (t2.unique2 = t1.unique2)
</programlisting>
La restriction sur <structname>tenk1</structname>,
<literal>unique1 < 50</literal>, est évaluée avant la jointure de boucle
imbriquée. Ceci est géré de façon analogue à l'exemple précédent. Cette fois, la
valeur 50 est dans la première partie de l'histogramme
<structfield>unique1</structfield> :
<programlisting>selectivity = (0 + (50 - bucket[1].min)/(bucket[1].max - bucket[1].min))/num_buckets
= (0 + (50 - 0)/(993 - 0))/10
= 0.005035
rows = 10000 * 0.005035
= 50 (rounding off)
</programlisting>
La restriction pour la jointure est <literal>t2.unique2 =
t1.unique2</literal>.
L'opérateur est tout simplement le <literal>=</literal>, néanmoins la
fonction de sélectivité est obtenue à partir de la colonne
<structfield>oprjoin</structfield> de
<structname>pg_operator</structname>, et est <function>eqjoinsel</function>.
<function>eqjoinsel</function> recherche l'information statistique de
<structname>tenk2</structname> et <structname>tenk1</structname> :
<programlisting>
SELECT tablename, null_frac,n_distinct, most_common_vals FROM pg_stats
WHERE tablename IN ('tenk1', 'tenk2') AND attname='unique2';
tablename | null_frac | n_distinct | most_common_vals
-----------+-----------+------------+------------------
tenk1 | 0 | -1 |
tenk2 | 0 | -1 |
</programlisting>
Dans ce cas, il n'y a pas d'information <acronym>MCV</acronym> pour
<structfield>unique2</structfield> parce que toutes les valeurs semblent
être unique, donc nous utilisons un algorithme qui relie seulement le
nombre de valeurs distinctes pour les deux relations ensembles avec leur
fractions NULL :
<programlisting>selectivity = (1 - null_frac1) * (1 - null_frac2) * min(1/num_distinct1, 1/num_distinct2)
= (1 - 0) * (1 - 0) / max(10000, 10000)
= 0.0001
</programlisting>
C'est-à-dire, soustraire la fraction NULL pour chacune des relations, et
divisez par le maximum of the numbers of distinct values. Le nombre de
lignes que la jointure pourrait émettre est calculé comme la cardinalité du
produit cartésien de deux inputs, multiplié par la sélectivité :
<programlisting>
rows = (outer_cardinality * inner_cardinality) * selectivity
= (50 * 10000) * 0.0001
= 50
</programlisting>
</para>
<para>
S'il y avait eu des listes MCV pour les deux colonnes,
<function>eqjoinsel</function> aurait utilisé une comparaison directe des
listes MCV pour déterminer la sélectivité de jointure à l'intérieur de la
aprtie des populations de colonne représentées par les MCV. L'estimation
pour le reste des populations suit la même approche affichée ici.
</para>
<para>
Notez que nous montrons <literal>inner_cardinality</literal> comme 10000,
c'est-à-dire la taille non modifiée de <structname>tenk2</structname>. Il
pourrait apparaître en inspectant l'affichage <command>EXPLAIN</command>
que l'estimation des lignes jointes vient de 50 * 1, c'est-à-dire que le
nombre de lignes externes multiplié par le nombre estimé de lignes obtenu
par chaque parcours d'index interne sur <structname>tenk2</structname>.
Mais ce n'est pas le cas : la taille de la relation jointe est
estimée avant tout plan de jointure particulier considéré. Si tout
fonctionne si bien, alors les deux façons d'estimer la taille de la jointure
produiront la même réponse mais, à cause de l'erreur d'arrondi et d'autres
facteurs, ils divergent quelque fois significativement.
</para>
<para>
Pour les personnes intéressées par plus de détails, l'estimation de la taille
d'une table (avant toute clause <literal>WHERE</literal>) se fait dans
<filename>src/backend/optimizer/util/plancat.c</filename>. La logique
générique pour les sélectivités de clause est dans
<filename>src/backend/optimizer/path/clausesel.c</filename>. Les fonctions
de sélectivité spécifiques aux opérateurs se trouvent principalement dans
<filename>src/backend/utils/adt/selfuncs.c</filename>.
</para>
</sect1>
</chapter>