... dans une requĂȘte bien conçue avec des conseils contextuels pour les nĆuds de plan correspondants:
Dans cette transcription de la deuxiÚme partie de mon discours à PGConf.Russia 2020, je vais vous dire comment nous avons réussi à faire cela.
La transcription de la premiĂšre partie, qui traite des problĂšmes typiques de performance des requĂȘtes et de leurs solutions, se trouve dans l'article «Recettes pour les requĂȘtes SQL dĂ©faillantes» .
Tout d'abord, nous allons peindre - et nous ne peindrons plus le plan, nous l'avons déjà peint, nous l'avons déjà beau et compréhensible, mais une demande.
Il nous a semblĂ© que la requĂȘte extraite du journal en utilisant une "feuille" non formatĂ©e semble trĂšs moche et donc peu pratique.
Surtout quand les dĂ©veloppeurs dans le code "collent" le corps de la requĂȘte (c'est bien sĂ»r un anti-pattern, mais ça arrive) sur une seule ligne. Horreur!
Dessinons-le en quelque sorte plus magnifiquement.
Et si nous pouvons le dessiner magnifiquement, c'est-Ă -dire dĂ©monter et remonter le corps de la requĂȘte, nous pouvons alors attacher un indice Ă chaque objet de cette requĂȘte - ce qui s'est passĂ© au point correspondant du plan.
Arborescence de requĂȘtes de syntaxe
Pour ce faire, la demande doit d'abord ĂȘtre analysĂ©e.
Ătant donnĂ© que notre noyau systĂšme fonctionne sur NodeJS , nous avons crĂ©Ă© des modules pour cela, vous pouvez le trouver sur GitHub . En fait, ce sont des "liaisons" Ă©tendues aux composants internes de l'analyseur PostgreSQL lui-mĂȘme. Autrement dit, la grammaire est simplement compilĂ©e en binaire et des liaisons y sont effectuĂ©es du cĂŽtĂ© NodeJS. Nous avons pris les modules d'autres personnes comme base - il n'y a pas de grand secret ici.
Nous transmettons le corps de la requĂȘte Ă l'entrĂ©e de notre fonction - Ă la sortie, nous obtenons un arbre de syntaxe analysĂ© sous la forme d'un objet JSON.
Vous pouvez maintenant parcourir cet arbre dans la direction opposĂ©e et collecter la requĂȘte avec les retraits, la coloration, la mise en forme que nous voulons. Non, ce n'est pas configurable, mais il nous a semblĂ© que ce serait pratique.
RequĂȘte de mappage et nĆuds de plan
Voyons maintenant comment nous pouvons combiner le plan que nous avons analysĂ© dans la premiĂšre Ă©tape et la requĂȘte que nous avons analysĂ©e dans la seconde.
Prenons un exemple simple - nous avons une requĂȘte qui gĂ©nĂšre un CTE et le lit deux fois. Il gĂ©nĂšre un tel plan.
CTE
Si vous regardez attentivement, avant la 12e version (ou à partir de celle-ci avec le mot-clé
MATERIALIZED
), la formation de CTE est une barriĂšre absolue pour le planificateur .
Cela signifie que si nous voyons la gĂ©nĂ©ration de CTE quelque part dans la requĂȘte et quelque part dans le plan un nĆud
CTE
, alors ces nĆuds "se battent" dĂ©finitivement les uns avec les autres, nous pouvons immĂ©diatement les combiner.
ProblĂšme d'astĂ©risque : les CTE peuvent ĂȘtre imbriquĂ©s.
Il y a trĂšs mal imbriquĂ©, et mĂȘme les mĂȘmes noms. Par exemple, vous pouvez le
CTE A
faire à l' intérieur CTE X
, et CTE B
le refaire au mĂȘme niveau Ă l'intĂ©rieur CTE X
:
WITH A AS (
WITH X AS (...)
SELECT ...
)
, B AS (
WITH X AS (...)
SELECT ...
)
...
Vous devez comprendre cela lorsque vous comparez. Il est trĂšs difficile de comprendre cela avec des «yeux» - mĂȘme en voyant le plan, voire en voyant le corps de la demande. Si votre gĂ©nĂ©ration CTE est complexe, imbriquĂ©e, les demandes sont volumineuses, alors elle est complĂštement inconsciente.
SYNDICAT
Si nous avons un mot-clĂ© dans la requĂȘte
UNION [ALL]
(l'opĂ©rateur de jonction de deux sĂ©lections), alors soit un nĆud Append
ou un autre lui correspond dans le plan Recursive Union
.
Ce qui est "au-dessus" est au-dessus
UNION
est le premier enfant de notre nĆud, ce qui est "en dessous" est le second. Si UNION
plusieurs blocs sont "collés" à travers nous à la fois, il Append
n'y aura toujours qu'un seul nĆud, mais il n'aura pas deux enfants, mais plusieurs - dans l'ordre au fur et Ă mesure, respectivement:
(...) -- #1
UNION ALL
(...) -- #2
UNION ALL
(...) -- #3
Append
-> ... #1
-> ... #2
-> ... #3
ProblÚme "avec un astérisque" : à l'intérieur de la génération d'une sélection récursive (
WITH RECURSIVE
), il peut aussi y en avoir plusieurs UNION
. Mais seul le tout dernier bloc aprÚs le dernier est toujours récursif UNION
. Tout ce qui précÚde est un mais différent UNION
:
WITH RECURSIVE T AS(
(...) -- #1
UNION ALL
(...) -- #2,
UNION ALL
(...) -- #3, T
)
...
Vous devez Ă©galement ĂȘtre capable de «coller» de tels exemples. Dans cet exemple, nous voyons qu'il
UNION
y avait 3 segments dans notre requĂȘte. En consĂ©quence, l'un UNION
correspond Ă un Append
-node, et l'autre correspond Ă Recursive Union
.
Données en lecture-écriture
Ăa y est, on l'Ă©tale, maintenant on sait quelle partie de la demande correspond Ă quelle partie du plan. Et dans ces piĂšces, nous pouvons facilement et naturellement trouver ces objets qui sont «lisibles».
Du point de vue de la requĂȘte, on ne sait pas s'il s'agit d'une table ou d'un CTE, mais ils sont dĂ©signĂ©s par le mĂȘme nĆud
RangeVar
. Et en termes de "lisible" - c'est aussi un ensemble assez limitĂ© de nĆuds:
Seq Scan on [tbl]
Bitmap Heap Scan on [tbl]
Index [Only] Scan [Backward] using [idx] on [tbl]
CTE Scan on [cte]
Insert/Update/Delete on [tbl]
Nous connaissons la structure du plan et de la requĂȘte, nous connaissons la correspondance des blocs, nous connaissons les noms des objets - nous faisons une comparaison sans ambiguĂŻtĂ©.
Encore une fois, un problĂšme d'astĂ©risque . Nous prenons la requĂȘte, l'exĂ©cutons, nous n'avons pas d'alias - nous la lisons juste deux fois Ă partir d'un CTE.
Nous regardons le plan - quel est le problÚme? Pourquoi notre pseudonyme est-il sorti? Nous ne l'avons pas commandé. Pourquoi est-il si "numéroté"?
PostgreSQL l'ajoute lui-mĂȘme. Vous avez juste besoin de comprendre qu'un tel alias n'a aucun sens pour nous Ă des fins de comparaison avec le plan, il est simplement ajoutĂ© ici. Ne faisons pas attention Ă lui.
La deuxiĂšme tĂąche est "avec un astĂ©risque" : si nous lisons Ă partir d'une table partitionnĂ©e, alors nous obtiendrons un nĆud
Append
ouMerge Append
, Qui se composera d'un grand nombre de « enfants », et dont chacun est en quelque sorte Scan
« e de la section de la table: Seq Scan
, Bitmap Heap Scan
ou Index Scan
. Mais, dans tous les cas, ces "enfants" ne seront pas des requĂȘtes complexes - c'est ainsi que ces nĆuds peuvent ĂȘtre distinguĂ©s de Append
quand UNION
.
Nous comprenons Ă©galement ces nĆuds, nous les rassemblons «en une seule pile» et disons: « tout ce que vous lisez Ă partir de mĂ©gatable est ici et en bas de l'arbre ».
NĆuds "simples" pour recevoir des donnĂ©es
Values Scan
dans le plan correspond VALUES
Ă la demande.
Result
- c'est une demande sans FROM
pareil SELECT 1
. Ou lorsque vous avez une expression sciemment fausse dans le WHERE
-bloc (alors l'attribut se produit One-Time Filter
):
EXPLAIN ANALYZE
SELECT * FROM pg_class WHERE FALSE; -- 0 = 1
Result (cost=0.00..0.00 rows=0 width=230) (actual time=0.000..0.000 rows=0 loops=1)
One-Time Filter: false
Function Scan
"Mapper" Ă la SRF du mĂȘme nom.
Mais avec les requĂȘtes imbriquĂ©es, tout est plus compliquĂ© - malheureusement, elles ne se transforment pas toujours en
InitPlan
/ SubPlan
. Parfois, ils se transforment en ... Join
ou ... Anti Join
, surtout lorsque vous Ă©crivez quelque chose comme WHERE NOT EXISTS ...
. Et il n'est pas toujours possible de combiner lĂ -bas - il n'y a pas d'opĂ©rateurs correspondant aux nĆuds du plan dans le texte du plan.
Encore une fois, une tùche avec un astérisque : plusieurs
VALUES
dans la requĂȘte. Dans ce cas et dans le plan, vous recevrez plusieurs nĆuds Values Scan
.
Les suffixes "numérotés" aideront à les distinguer les uns des autres - ils sont ajoutés exactement dans l'ordre de recherche des
VALUES
blocs correspondants le long de la requĂȘte de haut en bas.
Traitement de l'information
Il semble que tout dans notre demande a été réglé - il ne reste que
Limit
.
Mais tout est simple - comme les noeuds
Limit
, Sort
, Aggregate
, WindowAgg
, Unique
« mapyatsya » one-to-one aux déclarations correspondantes dans la demande, si elles sont là . Il n'y a pas de "stars" et pas de difficultés.
JOINDRE
Des difficultés surviennent lorsque nous voulons combiner les
JOIN
uns avec les autres. Ce n'est pas toujours fait, mais vous le pouvez.
Du point de vue de l'analyseur de requĂȘtes, nous avons un nĆud
JoinExpr
qui a exactement deux enfants - gauche et droite. Ceci, respectivement, est ce qui est "au-dessus" de votre JOIN et ce qui est "sous" dans la requĂȘte est Ă©crit.
Et du point de vue du plan, ce sont deux descendants de certains
* Loop
/ * Join
-node. Nested Loop
, Hash Anti Join
... - c'est quelque chose.
Utilisons une logique simple: si nous avons des plaques A et B qui "se rejoignent" dans le plan, alors dans la requĂȘte elles pourraient ĂȘtre localisĂ©es soit
A-JOIN-B
ou B-JOIN-A
. Essayons de le combiner de cette façon, essayons de le combiner dans l'autre sens, et ainsi de suite jusqu'à ce que ces paires soient épuisées.
Prenez notre arbre de syntaxe, prenez notre aperçu, regardez-les ... ça ne lui ressemble pas!
Redessinons-le sous forme de graphiques - oh, c'est déjà devenu quelque chose comme quelque chose!
Remarquons que nous avons des nĆuds qui ont des enfants B et C en mĂȘme temps - peu importe dans quel ordre. Combinons-les et retournons l'image du nĆud.
Voyons cela Ă nouveau. Maintenant, nous avons des nĆuds avec des enfants A et des paires (B + C) - compatibles avec eux aussi.
Excellent! Il s'avĂšre que nous avons
JOIN
rĂ©ussi Ă combiner ces deux Ă©lĂ©ments de la requĂȘte avec les nĆuds du plan.
Hélas, cette tùche n'est pas toujours résolue.
Par exemple, si dans la requĂȘte
A JOIN B JOIN C
, mais dans le plan, les nĆuds "extrĂȘmes" A et C Ă©taient connectĂ©s en premier. Et dans la requĂȘte, il n'y a pas d'opĂ©rateur de ce type, nous n'avons rien Ă mettre en Ă©vidence, il n'y a rien auquel lier l'indication. C'est la mĂȘme chose avec la "virgule" lorsque vous Ă©crivez A, B
.
Mais, dans la plupart des cas, presque tous les nĆuds peuvent ĂȘtre «dĂ©liĂ©s» et vous obtenez ce type de profilage Ă gauche dans le temps - littĂ©ralement, comme dans Google Chrome, lorsque vous analysez le code JavaScript. Vous pouvez voir combien de temps chaque ligne et chaque instruction ont Ă©tĂ© "exĂ©cutĂ©es".
Et pour que vous puissiez utiliser tout cela plus facilement, nous avons créé un stockage d' archives , dans lequel vous pouvez enregistrer puis retrouver vos plans avec les demandes associées ou partager un lien avec quelqu'un.
Si vous avez juste besoin de mettre une requĂȘte illisible sous une forme adĂ©quate, utilisez notre "normalisateur" .