Une question classique que les dĂ©veloppeurs posent presque toujours Ă leur DBA ou au consultant PostgreSQL est : « Pourquoi les requĂȘtes prennent-elles autant de temps Ă s'exĂ©cuter ? »
Un ensemble traditionnel de raisons :
- un algorithme inefficace
lorsque vous essayez de faire un JOIN entre plusieurs CTE avec des dizaines de milliers d'enregistrements - Ce point est particuliĂšrement important pour PostgreSQL, lorsque vous avez « dĂ©versĂ© » un grand ensemble de donnĂ©es sur le serveur, vous faites une requĂȘte â et celui-ci effectue une « scan sĂ©quentielle » sur la table. Parce qu'hier, il y avait 10 enregistrements, et aujourd'hui 10 millions, mais PostgreSQL n'est pas encore au courant, et il faut le lui signaler. [
si la distribution réelle des données dans la table diffÚre fortement de celle analysée par ANALYZE la derniÚre fois - Vous avez installé une grande base de données lourde sur un serveur faible, manquant d'espace disque, de mémoire et de puissance processeur. Et voilà ⊠Il y a un seuil de performance au-delà duquel vous ne pouvez plus sauter.
et si les capacités de calcul CPU dédiées ne suffisent plus, que des gigaoctets de mémoire sont constamment sollicités ou que le disque ne peut pas suivre toutes les « demandes » de la base de données - de blocage en raison de processus concurrents
Et si les blocages sont assez difficiles Ă capturer et Ă analyser, pour tout le reste, il nous suffit du plan de requĂȘte, que l'on peut obtenir en utilisant (idĂ©alement, utilisez EXPLAIN (ANALYZE, BUFFERS) âŠ) ou .
Cependant, comme le dit la documentation,
« Comprendre le plan est un art, et pour le maßtriser, une certaine expérience est nécessaire, ⊠»
Mais on peut s'en passer si l'on utilise un outil approprié !
Ă quoi ressemble gĂ©nĂ©ralement un plan de requĂȘte ? En quelque sorte comme cela :
Index Scan using pg_class_relname_nsp_index on pg_class (actual time=0.049..0.050 rows=1 loops=1)
Index Cond: (relname = $1)
Filter: (oid = $0)
Buffers: shared hit=4
InitPlan 1 (returns $0,$1)
-> Limit (actual time=0.019..0.020 rows=1 loops=1)
Buffers: shared hit=1
-> Seq Scan on pg_class pg_class_1 (actual time=0.015..0.015 rows=1 loops=1)
Filter: (relkind = 'r'::"char")
Rows Removed by Filter: 5
Buffers: shared hit=1ou comme ça :
"Append (cost=868.60..878.95 rows=2 width=233) (actual time=0.024..0.144 rows=2 loops=1)"
" Buffers: shared hit=3"
" CTE cl"
" -> Seq Scan on pg_class (cost=0.00..868.60 rows=9972 width=537) (actual time=0.016..0.042 rows=101 loops=1)"
" Buffers: shared hit=3"
" -> Limit (cost=0.00..0.10 rows=1 width=233) (actual time=0.023..0.024 rows=1 loops=1)"
" Buffers: shared hit=1"
" -> CTE Scan on cl (cost=0.00..997.20 rows=9972 width=233) (actual time=0.021..0.021 rows=1 loops=1)"
" Buffers: shared hit=1"
" -> Limit (cost=10.00..10.10 rows=1 width=233) (actual time=0.117..0.118 rows=1 loops=1)"
" Buffers: shared hit=2"
" -> CTE Scan on cl cl_1 (cost=0.00..997.20 rows=9972 width=233) (actual time=0.001..0.104 rows=101 loops=1)"
" Buffers: shared hit=2"
"Planning Time: 0.634 ms"
"Execution Time: 0.248 ms"Mais lire le plan textuellement « à la volée » est trÚs difficile et peu clair :
- dans le nĆud, est affichĂ© la somme des ressources de l'arbre sous-jacent
c'est-Ă -dire que pour comprendre combien de temps a Ă©tĂ© utilisĂ© pour exĂ©cuter un nĆud spĂ©cifique, ou combien de donnĂ©es ont Ă©tĂ© effectivement rĂ©cupĂ©rĂ©es de la table â il faut soustraire une valeur d'une autre - le temps du nĆud doit ĂȘtre multipliĂ© par les loops
Oui, la soustraction n'est pas l'opĂ©ration la plus difficile Ă faire « mentalement » â en effet, le temps d'exĂ©cution est indiquĂ© en moyenne pour un seul nĆud, alors qu'il peut y en avoir des centaines. - Eh bien, tout cela complique la rĂ©ponse Ă la question principale â alors, qui est-ce ? « Le maillon le plus faible »?
Lorsque nous avons essayé d'expliquer tout cela à plusieurs centaines de nos développeurs, nous avons réalisé que de l'extérieur, cela ressemblait à peu prÚs à cela :

Alors, nous avons besoin deâŠ
Outil
Dans celui-ci, nous avons essayé de rassembler toutes les mécaniques clés qui aident à comprendre, en fonction du plan et de la demande, « qui est responsable et que faire ». Et aussi, de partager une partie de notre expérience avec la communauté.
Faites connaissance et utilisez â
Clarté des plans
Est-il facile de comprendre un plan lorsqu'il ressemble Ă cela ?
Seq Scan on pg_class (temps réel=0.009..1.304 lignes=6609 boucles=1)
Buffers : partagés hit=263
Temps de planification : 0.108 ms
Temps d'exécution : 1.800 ms
Pas vraiment.
Mais ainsi, dans une version abrĂ©gĂ©e, lorsque les indicateurs clĂ©s sont sĂ©parĂ©s â c'est dĂ©jĂ beaucoup plus clair :

Mais si le plan est plus complexe â un diagramme Ă secteurs de distribution du temps par nĆuds :

Eh bien, pour les cas les plus complexes, le diagramme d'exécution:

Par exemple, il existe des situations suffisamment non triviales oĂč un plan peut avoir plus d'une racine rĂ©elle :


Conseils structurels
Eh bien, si toute la structure du plan et ses points faibles sont dĂ©jĂ Ă©tablis et visibles â pourquoi ne pas les mettre en Ă©vidence pour le dĂ©veloppeur, et expliquer « en termes simples » ?
Nous avons déjà rassemblé une bonne dizaine de ces modÚles de recommandations.
Profilage ligne par ligne de la requĂȘte
Maintenant, si vous appliquez la requĂȘte d'origine au plan analysĂ©, vous pouvez voir combien de temps chaque opĂ©rateur individuel a pris â Ă peu prĂšs comme cela :

⊠ou mĂȘme comme ça :

Substitution de paramĂštres dans la requĂȘte
Si vous avez « attachĂ© » au plan non seulement la requĂȘte, mais aussi ses paramĂštres depuis la ligne DETAIL du journal, vous pouvez la copier de maniĂšre supplĂ©mentaire dans l'une des variantes :
- avec substitution des valeurs dans la requĂȘte
pour une exécution immédiate sur votre propre base et un profilage ultérieurSELECT 'const', 'param'::text; - avec substitution des valeurs via PREPARE/EXECUTE
pour simuler le fonctionnement du planificateur, lorsque la partie paramĂ©trique peut ĂȘtre ignorĂ©e â par exemple, lors de l'utilisation de tables partitionnĂ©esDEALLOCATE ALL; PREPARE q(text) AS SELECT 'const', $1::text; EXECUTE q('param'::text);
Archives des plans
Insérez, analysez, partagez avec vos collÚgues ! Les plans resteront archivés, et vous pourrez y revenir plus tard :
Mais si vous ne voulez pas que votre plan soit vu par d'autres, n'oubliez pas de cocher la case « ne pas publier dans l'archive ».
Dans les prochains articles, je parlerai des difficultés et des solutions qui surgissent lors de l'analyse des plans.
Source : habr.com
