Mnohí, ktorí už používajú — našu službu vizualizácie plánov PostgreSQL, si možno nie sú vedomí jednej z jej super schopností — premieňať ťažko čitateľný kus logu servera…

… na pekne formátovaný dotaz s kontextovými nápovedami pre príslušné uzly plánu:

V tejto interpretácii druhej časti svojej povediem, ako sa nám to podarilo.
S prepisom prvej časti, ktorá sa týka typických problémov výkonnosti dotazov a ich riešení, sa môžete zoznámiť v článku .

Najprv sa zameriame na farebnenie — a farebnenie budeme robiť už nie plánu, ten sme už vyfarbili, je pre nás už pekný a zrozumiteľný, ale dotazu.
Zdalo sa nám, že takto neformátovaná „plachta“ vytrhnutá z logu dotazu vyzerá veľmi nepekné a preto — nepohodlne.

Obzvlášť, keď vývojári v kóde „lepia“ telo dotazu (to je samozrejme antipattern, ale stáva sa) do jedného riadku. Hrôza!
Poďme to nakresliť nejak krásnejšie.

A ak by sme to mohli nakresliť pekne, t.j. rozobrať a opäť zložiť telo dotazu, tak by sme potom mohli k každému objektu tohto dotazu „pripevniť“ nápovedu — čo sa dialo v príslušnom bode plánu.
Syntaktické strom dotazu
Aby sme to urobili, dotaz treba najprv rozobrať.

Keďže, u nás , tak sme urobili k nemu maličký modul, môžete . Naozaj, toto sú rozšírené „bindingy“ k vnútornostiam parsera samotného PostgreSQL. T.j. jednoducho binárne skompilovaná gramatika a k nej sú urobené bindingy zo strany NodeJS. Vzali sme za základ cudzie moduly — tu nie je veľké tajomstvo.
Krmiame telo dotazu na vstup našej funkcie — na výstupe dostaneme rozobraté syntaktické strom v podobe JSON-objektu.

Teraz sa po tomto strome dá prebehnúť naspäť a poskladať dotaz s tými odsadenými, farebnením, formátovaním, ktoré chceme. Nie, to nie je nastaviteľné, ale zdalo sa nám, že práve takto to bude pohodlné.

Priradenie uzlov dotazu a plánu
Teraz sa pozrieme, ako môžeme skombinovať plán, ktorý sme rozobrali v prvom kroku, a dotaz, ktorý sme rozobrali v druhom.
Zoberme si jednoduchý príklad — máme dotaz, ktorý vytvára CTE a dvakrát z nej číta. Generuje takýto plán.

CTE
Ak sa na to pozriete pozorne, pred 12. verziou (alebo začínajúc s ňou s kľúčovým slovom MATERIALIZED) tvorba .

A, to znamená, že ak niekde v dotaze vidíme generáciu CTE a niekde v pláne uzol CTE, tak tieto uzly sa určite "bijú", môžeme ich hneď zlúčiť.
Úloha s hviezdičkou: CTE môžu byť vnorené.

Môžu byť veľmi zle vnorené, a dokonca rovnomenne. Napríklad, môžete vo vnútri CTE A urobiť CTE X, a na tej istej úrovni vo vnútri CTE B urobiť opäť CTE X:
WITH A AS (
WITH X AS (...)
SELECT ...
)
, B AS (
WITH X AS (...)
SELECT ...
)
...Pri porovnávaní to musíte chápať. Pochopiť to "očami" — aj keď vidíte plán, aj keď vidíte telo dotazu — je veľmi ťažké. Ak máte zložitú, vnorenú generáciu CTE, veľké dotazy — potom to je úplne nevedomé.
operátor podporujú
Ak máme v dotaze kľúčové slovo UNION [ALL] (operátor spájania dvoch výberov), tak mu v pláne zodpovedá buď uzol Pridať, alebo nejaký Rekurzívna únia.

To, čo "zhora" nad operátor podporujú — to je prvý potomok nášho uzla, čo "zdola" — druhý. Ak cez operátor podporujú máme "prilepené" niekoľko blokov naraz, tak Pridať-uzol bude stále len jeden, ale detí bude mať nie dva, ale mnoho — po poradí, ako idú, zodpovedajúco:
(...) -- #1
UNION ALL
(...) -- #2
UNION ALL
(...) -- #3Append
-> ... #1
-> ... #2
-> ... #3
Úloha s hviezdičkou: vo vnútri generácie rekurzívneho výberu (WITH RECURSIVE) môže byť tiež viac ako jeden. operátor podporujú. Ale vždy rekurzívny je iba posledný blok po poslednom operátor podporujú. Všetko, čo je hore — to je jeden, ale iný operátor podporujú:
WITH RECURSIVE T AS(
(...) -- #1
UNION ALL
(...) -- #2, tu sa končí generácia počiatočného stavu rekurzie
UNION ALL
(...) -- #3, iba tento blok je rekurzívny a môže obsahovať odkaz na T
)
... Takéto príklady musíte vedieť "rozlepovať". Tu v tomto príklade vidíme, že operátor podporujú-segmentov v našom dotaze bolo 3 kusy. Zodpovedajúce, jednému operátor podporujú zodpovedá Pridať-uzlu, a druhému — Rekurzívna únia.

Čítanie-zápis údajov
Všetko, rozložili sme, teraz vieme, ktorý kúsok dotazu zodpovedá ktorému kúsku plánu. A v týchto kúsoch môžeme ľahko a bez námahy nájsť tie objekty, ktoré sa "čítajú".
Z pohľadu dotazu nevieme — či ide o tabuľku alebo CTE, ale označujú sa rovnakým uzlom. Rozsahová premenná. A v pláne „čítať“ — to tiež predstavuje dosť obmedzenú skupinu uzlov:
Sekvenčné skenovanie na [tbl]Bitmapová hromadná kontrola na [tbl]Index [Only] Scan [Backward] using [idx] on [tbl]CTE skenovanie na [cte]Vložiť/Aktualizácia/Delete na [tbl]
Štruktúru plánu a dopytu poznáme, zaradenie blokov poznáme, mená objektov poznáme — robíme jednoznačné priradenie.

Opäť raz úloha „so hviezdičkou“. Berieme dopyt, vykonávame ho, nemáme žiadne aliasy — jednoducho sme dvakrát prečítali z jednej CTE.

Pozeráme do plánu — čo je zle? Prečo nám vyskočil alias? Nežiadali sme ho. Odkiaľ sa vzal taký „číslený“?
PostgreSQL ho pridáva samo. Treba si len uvedomiť, že taký alias pre nás na účely priradenia k plánu nemá žiadny zmysel, bol tu jednoducho pridaný. Nevenujme mu pozornosť.
Druhá úloha „so hviezdičkou“: ak čítame zo sekciou rozdelené tabuľky, dostaneme uzol Pridať alebo Zlúčiť Pripojiť, ktorý bude obsahovať veľké množstvo „detí“, a každé z nich bude nejaké Scan‘om z tabuľky-sekcie: Sekvenčné skenovanie, Bitmapová hromadná kontrola alebo Index Scan. Ale v každom prípade, tieto „deti“ nebudú zložitými dopytmi — takto sa tieto uzly dajú rozlíšiť od Pridať pri operátor podporujú.

Takéto uzly tiež chápeme, zbierame ich „do jednoho balíka“ a hovoríme: "všetko, čo si čítal z megatable — to je tu a nižšie v strome".
„Jednoduché“ uzly získavania dát

Skenovanie hodnôt v pláne zodpovedá HODNOTY v dopyte.
Výsledok — to je dopyt bez FROM akoby SELECT 1. Alebo keď máte zakaždým nepravdivý výraz v WHERE-bloku (potom vzniká atribút One-Time Filter):
EXPLAIN ANALYZE
SELECT * FROM pg_class WHERE FALSE; -- alebo 0 = 1Výsledok (náklady=0.00..0.00 riadkov=0 šírka=230) (skutočný čas=0.000..0.000 riadkov=0 slučiek=1)
One-Time Filter: false
Funkcia skenovania „mapujú“ sa na rovnaké SRF.
Ale so zloženými dopytmi je to ťažšie — žiaľ, neprevádzajú sa vždy na Základný plán/SubPlán. Niekedy sa prevádzajú na ... Join alebo ... Anti Join, najmä keď napíšete niečo ako WHERE NOT EXISTS .... A tam nie je vždy možné ich kombinovať — v texte plánu nie sú zodpovedajúce uzlom plánu operátorov.
Opäť raz úloha „so hviezdičkou“: niekoľko HODNOTY v dopyte. V tomto prípade dostanete v pláne niekoľko uzlov Skenovanie hodnôt.

Rozlíšiť ich navzájom pomôžu „číslené“ prípony — pridávajú sa presne v poradí nájdenia zodpovedajúcich HODNOTY-blokov počas dopytu zhora nadol.
Spracovanie údajov
Akoby sme všetko v našom dopyte rozobrali — ostáva len Obmedzenie.

Ale tu je to jednoduché — také uzly ako Obmedzenie, Tried, Agregovať, WindowAgg, Unique „mapujú“ sa jeden-za-jedným na zodpovedajúce operátory v dopyte, ak tam sú. Tu nie sú žiadne „hviezdičky“ a zložitosti.

JOIN
Zložitosti vznikajú, keď sa chceme skombinovať JOIN navzájom. Urobiť to nie je vždy možné, ale dá sa.

Z pohľadu parsu dotazu máme uzol PripojiťExpr, ktorý má presne dva potomkov — ľavý a pravý. To, čo je „nad“ vaším JOIN a to, čo je „pod“ ním v dotaze.
A z pohľadu plánu sú to dva potomkov nejakej * Cyklus/* Pripojiť-uzla. Vnorená slučka, Hash Anti Join,… — niečo také.
Použijeme jednoduchú logiku: ak máme tabuľky A a B, ktoré „sa spoja“ v pláne, mohli by byť v dotaze umiestnené buď A-JOIN-B, alebo B-JOIN-A. Skúsime to kombinovať takto, skúsime to otočiť a takto pokračujeme, kým sa také páry neskončia.
Zoberme si naše syntaktické strom, vezmeme náš plán, pozrieme sa na ne... nezdá sa to!

Prekreslíme to ako grafy — o, už to začína vyzerať ako niečo!

Dáva nám pozor, že máme uzly, ktoré majú zároveň deti B a C — nezáleží nám na poradí. Skombinujeme ich a otočíme kresbu uzla.

Pozrime sa znova. Teraz máme uzly s deťmi A a párom (B + C) — skombinujeme aj tie.

Skvelé! To znamená, že sme úspešne skombinovali tieto dva JOIN z dotazu s uzlami plánu.
Žiaľ, táto úloha nie je vždy riešiteľná.

Napríklad, ak v dotaze A JOIN B JOIN C, a v pláne sa najprv spojili „extrémne“ uzly A a C. A v dotaze nie je taký operátor, nemáme čo zvýrazniť, nie je k čomu pripojiť návod. To isté platí pre „čiarku“, keď píšete A, B.
Ale vo väčšine prípadov sa takmer všetky uzly podarí „rozviazať“ a získať takýto profilovanie vľavo podľa času — doslova, ako v Google Chrome, keď analyzujete kód v JavaScripte. Vidíte, koľko času trvala každá riadok a každý operátor „vykonával“.

A aby ste sa s tým všetkým ľahšie používali, vytvorili sme úložisko , kde si môžete uložiť a neskôr nájsť svoje plány spolu s priradenými dotazmi alebo sa s niekým podeliť o odkaz.
Ak potrebujete len premeniť nečitateľný dotaz na primeranú podobu, využite .

Zdroj: habr.com
