PostgreSQL Query Profiler: ako spárovať plán a dopyt
Mnohí, ktorí už používajú vysvetliť.tensor.ru - naša služba vizualizácie plánu PostgreSQL si možno neuvedomuje jednu zo svojich superschopností - otáčanie ťažko čitateľného kusu denníka servera...
... do krásne navrhnutého dotazu s kontextovými radami pre zodpovedajúce uzly plánu:
Prepis prvej časti venovanej typickým problémom s výkonom dotazov a ich riešeniam nájdete v článku "Recepty na choré SQL dotazy".
Najprv začneme vyfarbovať - a plán už nebudeme vyfarbovať, už sme ho vyfarbili, už ho máme krásny a zrozumiteľný, ale prosba.
Zdalo sa nám, že pri takomto nenaformátovanom „hárku“ vyzerá požiadavka vytiahnutá z guľatiny veľmi škaredo a teda nepohodlne.
Najmä, keď vývojári „nalepia“ telo požiadavky v kóde (toto je, samozrejme, antivzor, ale stáva sa to) do jedného riadku. Strašné!
Poďme si to nakresliť nejako krajšie.
A ak to dokážeme krásne nakresliť, teda rozobrať a poskladať telo požiadavky, potom môžeme ku každému predmetu tejto požiadavky „pripojiť“ nápovedu – čo sa stalo v príslušnom bode plánu.
Strom syntaxe dotazu
Aby ste to dosiahli, musíte žiadosť najskôr analyzovať.
Pretože máme jadro systému beží na NodeJS, potom sme na to urobili modul, môžete nájdete ho na GitHub. V skutočnosti ide o rozšírené „väzby“ na vnútorné časti samotného analyzátora PostgreSQL. To znamená, že gramatika je jednoducho binárne skompilovaná a väzby sú k nej vytvorené z NodeJS. Ako základ sme vzali moduly iných ľudí - tu nie je žiadne veľké tajomstvo.
Telo požiadavky podávame ako vstup do našej funkcie – na výstupe dostaneme syntaktický strom syntaxe vo forme objektu JSON.
Teraz môžeme prejsť cez tento strom v opačnom smere a zostaviť požiadavku s požadovanými odrážkami, farbením a formátovaním. Nie, toto nie je prispôsobiteľné, ale zdalo sa nám, že by to bolo pohodlné.
Mapovací dotaz a uzly plánu
Teraz sa pozrime, ako môžeme skombinovať plán, ktorý sme analyzovali v prvom kroku, a dotaz, ktorý sme analyzovali v druhom kroku.
Uveďme si jednoduchý príklad – máme dotaz, ktorý generuje CTE a číta z neho dvakrát. Vytvorí takýto plán.
To znamená, že ak niekde v požiadavke vidíme generovanie CTE a niekde v pláne uzol CTE, potom tieto uzly medzi sebou určite „bojujú“, môžeme ich hneď kombinovať.
Problém s hviezdičkou: CTE môžu byť vnorené.
Existujú veľmi zle vnorené a dokonca aj tie s rovnakým názvom. Môžete napríklad dovnútra CTE A robiť CTE Xa na rovnakej úrovni vo vnútri CTE B sprav to znovu CTE X:
WITH A AS (
WITH X AS (...)
SELECT ...
)
, B AS (
WITH X AS (...)
SELECT ...
)
...
Pri porovnávaní to musíte pochopiť. Pochopiť to „vašimi očami“ - dokonca vidieť plán, dokonca vidieť telo žiadosti - je veľmi ťažké. Ak je vaša generácia CTE zložitá, vnorená a požiadavky sú veľké, potom je to úplne v bezvedomí.
UNION
Ak máme v dopyte kľúčové slovo UNION [ALL] (operátor spojenia dvoch vzoriek), potom v pláne zodpovedá buď uzlu Append, alebo nejaké Recursive Union.
To, čo je „hore“ vyššie UNION - toto je prvý potomok nášho uzla, ktorý je „dole“ - druhý. Ak cez UNION máme teda „zlepených“ niekoľko blokov naraz Append-stále bude len jeden uzol, ale nebude mať dve, ale veľa detí - v poradí, v akom idú, resp.
(...) -- #1
UNION ALL
(...) -- #2
UNION ALL
(...) -- #3
Append
-> ... #1
-> ... #2
-> ... #3
Problém s hviezdičkou: v rámci generovania rekurzívneho vzorkovania (WITH RECURSIVE) môže byť aj viac ako jeden UNION. Ale len úplne posledný blok po poslednom je vždy rekurzívny UNION. Všetko vyššie je jedno, ale iné UNION:
WITH RECURSIVE T AS(
(...) -- #1
UNION ALL
(...) -- #2, тут кончается генерация стартового состояния рекурсии
UNION ALL
(...) -- #3, только этот блок рекурсивный и может содержать обращение к T
)
...
Musíte byť tiež schopní „vypichnúť“ takéto príklady. V tomto príklade to vidíme UNION-v našej žiadosti boli 3 segmenty. Podľa toho jeden UNION zodpovedá Append-uzol a do druhého - Recursive Union.
Čítanie a zápis údajov
Všetko je rozložené, teraz vieme, ktorá časť požiadavky zodpovedá ktorej časti plánu. A v týchto kúskoch ľahko a prirodzene nájdeme tie predmety, ktoré sú „čitateľné“.
Z hľadiska dopytu nevieme, či ide o tabuľku alebo CTE, ale sú označené rovnakým uzlom RangeVar. A pokiaľ ide o „čitateľnosť“, ide tiež o pomerne obmedzený súbor uzlov:
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]
Poznáme štruktúru plánu a dopytu, poznáme korešpondenciu blokov, poznáme názvy objektov – robíme porovnanie jedna ku jednej.
Opäť úloha "s hviezdičkou". Prijmeme požiadavku, vykonáme ju, nemáme žiadne aliasy – len ju čítame dvakrát z toho istého CTE.
Pozeráme sa na plán – v čom je problém? Prečo sme mali alias? Neobjednali sme si to. Odkiaľ berie také „číslo“?
PostgreSQL to pridáva sám. Len to musíš pochopiť len taký alias pre nas to na ucely porovnania s planom nedava ziadny zmysel, jednoducho sa to tu pridava. Nevšímajme si ho.
Druhý úloha "s hviezdičkou": ak čítame z rozdelenej tabuľky, dostaneme uzol Append alebo Merge Append, ktorý bude pozostávať z veľkého počtu „detí“ a každé z nich bude nejako Scan'om zo sekcie tabuľky: Seq Scan, Bitmap Heap Scan alebo Index Scan. V každom prípade však tieto „deti“ nebudú zložité otázky - takto možno tieto uzly odlíšiť od Append na UNION.
Rozumieme aj takýmto uzlom, zbierame ich „na jednu hromadu“ a hovoríme: „všetko, čo čítate z megatable, je tu a dole na strome".
"Jednoduché" uzly na príjem údajov
Values Scan zodpovedá plánom VALUES v žiadosti.
Result je žiadosť bez FROM niečo ako SELECT 1. Alebo keď máte úmyselne falošný výraz WHERE-block (potom sa objaví atribút One-Time Filter):
EXPLAIN ANALYZE
SELECT * FROM pg_class WHERE FALSE; -- или 0 = 1
Ale s vnorenými dopytmi je všetko komplikovanejšie - bohužiaľ, nie vždy sa zmenia na InitPlan/SubPlan. Niekedy sa zmenia na ... Join alebo ... Anti Join, hlavne keď napíšeš niečo ako WHERE NOT EXISTS .... A tu nie je vždy možné ich kombinovať - v texte plánu nie sú žiadne operátory zodpovedajúce uzlom plánu.
Opäť úloha "s hviezdičkou": niektoré VALUES v žiadosti. V tomto prípade a v pláne získate niekoľko uzlov Values Scan.
„Očíslované“ prípony ich pomôžu odlíšiť od seba - pridávajú sa presne v poradí, v akom sa nachádzajú zodpovedajúce VALUES-blokuje pozdĺž požiadavky zhora nadol.
Spracovanie dát
Zdá sa, že všetko v našej žiadosti bolo vyriešené – zostáva už len všetko Limit.
Ale tu je všetko jednoduché - také uzly ako Limit, Sort, Aggregate, WindowAgg, Unique „mapovať“ jeden k jednému na zodpovedajúcich operátorov v požiadavke, ak tam sú. Nie sú tu žiadne „hviezdy“ ani ťažkosti.
REGISTRÁCIA
Ťažkosti vznikajú, keď chceme kombinovať JOIN medzi sebou. Nie je to vždy možné, ale je to možné.
Z pohľadu analyzátora dotazov máme uzol JoinExpr, ktorá má rovno dve deti – ľavé a pravé. Toto je teda to, čo je „nad“ vaším JOINom a čo je napísané „pod“ v žiadosti.
A z pohľadu plánu ide o dvoch potomkov niektorých * Loop/* Join- uzol. Nested Loop, Hash Anti Join,... - niečo také.
Použime jednoduchú logiku: ak máme tabuľky A a B, ktoré sa navzájom „spájajú“ v pláne, potom v požiadavke môžu byť umiestnené buď A-JOIN-BAlebo B-JOIN-A. Skúsme kombinovať takto, skúsme kombinovať naopak a tak ďalej, kým sa nám takéto páry neminú.
Vezmime si náš syntaktický strom, zoberme si náš plán, pozrime sa na ne... nie podobné!
Prekreslíme si to do podoby grafov – ach, už to na niečo vyzerá!
Všimnime si, že máme uzly, ktoré majú súčasne deti B a C - je nám jedno, v akom poradí. Skombinujme ich a obrátime obrázok uzla.
Pozrime sa znova. Teraz máme uzly s deťmi A a pármi (B + C) - kompatibilné aj s nimi.
Skvelé! Ukazuje sa, že my dvaja sme JOIN z požiadavky s plánom uzly boli úspešne spojené.
Bohužiaľ, tento problém nie je vždy vyriešený.
Napríklad, ak v žiadosti A JOIN B JOIN C, a v pláne boli v prvom rade spojené „vonkajšie“ uzly A a C. Ale v požiadavke takýto operátor nie je, nemáme čo zvýrazniť, k čomu pripojiť nápovedu. Rovnako je to aj s „čiarkou“, keď píšete A, B.
Vo väčšine prípadov však možno takmer všetky uzly „rozviazať“ a tento druh profilovania môžete získať na ľavej strane včas – doslova ako v prehliadači Google Chrome, keď analyzujete kód JavaScript. Môžete vidieť, ako dlho trvalo „vykonanie“ každého riadku a každého príkazu.
A aby bolo pre vás používanie tohto všetkého pohodlnejšie, vytvorili sme úložisko archív, kde si môžete uložiť a neskôr nájsť svoje plány spolu s priradenými požiadavkami alebo s niekým zdieľať odkaz.
Ak potrebujete len preniesť nečitateľný dotaz do adekvátnej podoby, použite náš "normalizátor".