Mnogi koji već koriste — naš servis za vizualizaciju planova PostgreSQL, možda nisu upoznati s jednom od njegovih supermoći — pretvaranjem teško čitljivog djela server loga…

… u lijepo oblikovan upit s kontekstualnim savjetima o odgovarajućim čvorovima plana:

U ovom razotkrivanju druge polovice svog ispričat ću kako smo to uspjeli ostvariti.
S transkripcijom prve polovice, posvećene tipičnim problemima performansi upita i njihovim rješenjima, možete se upoznati u članku .

Prvo ćemo se posvetiti bojanju — a bojat ćemo već ne upit, njega smo već obojili, on nam je sada lijep i razumljiv, nego bar to.
Činilo nam se da ovakva neformatirana 'plahata' izvučena iz loga izgleda jako ružno i zbog toga — neugodno.

Osobito kada programeri u kodu 'zalijepe' tijelo upita (to je, naravno, antipaterni obrazac, ali se događa) u jedan redak. Užas!
Hajdemo to nacrtati nekako ljepše.

A ako možemo to lijepo nacrtati, to jest rastaviti i ponovno sastaviti tijelo upita, tada ćemo moći i svakom objektu ovog upita 'priključiti' savjet — što se događalo na odgovarajućoj točki plana.
Sintaktičko stablo upita
Da bismo to učinili, upit prvo treba rastaviti.

Budući da , napravili smo modul za to, možete . U stvarnosti, ovo su prošireni 'bindingi' za unutarnje dijelove samog PostgreSQL parsera. Drugim riječima, jednostavno binarno kompilirana gramatika i za nju su napravljeni bindingi sa strane NodeJS. Kao osnovu smo uzeli tuđe module — tu nema velikih tajni.
Dajemo tijelo upita kao ulaz našoj funkciji — na izlazu dobivamo rastavljeno sintaktičko stablo u obliku JSON objekta.

Sada se po ovom stablu može proći unatrag i sastaviti upit s onim razmacima, bojanjem, formatiranjem koje želimo. Ne, to se ne može prilagoditi, ali činilo nam se da će ovako biti zgodno.

Usporedba čvorova upita i plana
Sada ćemo vidjeti kako možemo spojiti plan koji smo analizirali u prvom koraku i upit koji smo analizirali u drugom.
Uzmimo jednostavan primjer — imamo upit koji formira CTE i dvaput iz njega čita. Generira takav plan.

CTE
Ako se pažljivo pogleda, do 12. verzije (ili počevši od nje s ključnom riječi MATERIALIZED) formiranje .

Znači, ako negdje u upitu vidimo generaciju CTE i negdje u planu čvor CTE, tada se ti čvorovi definitivno međusobno "udare", odmah ih možemo spojiti.
Zadatak "s zvjezdicom": CTE mogu biti ugniježđeni.

Mogu biti vrlo loše ugniježđeni, pa čak i istog naziva. Na primjer, možete unutar CTE A napraviti CTE X, i na istoj razini unutar CTE B ponovno napraviti CTE X:
WITH A AS (
WITH X AS (...)
SELECT ...
)
, B AS (
WITH X AS (...)
SELECT ...
)
...Prilikom usklađivanja to morate razumjeti. Razumjeti to "očima" — čak i vidjevši plan, čak i vidjevši tijelo upita — vrlo je teško. Ako imate složenu, ugniježđenu generaciju CTE, velike upite — tada to postaje potpuno neosviješteno.
UNION
Ako imamo ključnu riječ UNION [ALL] (operator spajanja dviju selekcija), tada mu u planu odgovara ili čvor Dodaj, ili neka vrsta Rekurzivna unija.

Ono što je "iznad" našeg čvora je prvi potomak našeg čvora, a što je "ispod" — drugi. Ako je UNION imamo "zalijepljeno" nekoliko blokova odjednom, tada UNION -čvor će i dalje biti samo jedan, ali će imati više djece — redoslijedom kako dolaze, prema tome: Dodaj(...) -- #1 UNION ALL (...) -- #2 UNION ALL (...) -- #3
Append
-> ... #1
-> ... #2
-> ... #3: unutar generacije rekurzivne selekcije (
Zadatak "s zvjezdicom"WITH RECURSIVE) također može biti više od jednog. No uvijek je rekurzivan samo posljednji blok nakon posljednjeg UNION. Sve što je iznad — to je jedan, ali drugi UNIONWITH RECURSIVE T AS( (...) -- #1 UNION ALL (...) -- #2, ovdje završava generacija početnog stanja rekurzije UNION ALL (...) -- #3, samo je ovaj blok rekurzivan i može sadržavati pozivanje na T ) ... UNION:
Takve primjere također treba znati "raspasti". Evo u ovom primjeru vidimo da -segmenata u našem upitu bilo je 3 komada. Prema tome, jednom UNION-čvoru, a drugom — UNION odgovara DodajČitanje-upis podataka Rekurzivna unija.

Sve, razložili smo, sada znamo koji dio upita kojem dijelu plana odgovara. I u tim dijelovima možemo lako i nesmetano pronaći one objekte koji se "čitaju".
S aspekta upita ne znamo — je li to tablica ili CTE, ali označavaju se istim čvorom.
Što se tiče upita, ne znamo je li to tablica ili CTE, ali se označavaju istim čvorom RasponVar. A u planu "čitljivo" — to je također prilično ograničen skup čvorova:
Sekvencijalno skeniranje na [tbl]Bitmap Heap Scan na [tbl]Indeks [Samo] Skener [Unazad] koristeći [idx] na [tbl]CTE skeniranje na [cte]Umetnite/Ažurirajte/Obriši na [tbl]
Strukturu plana i upita znamo, usklađenost blokova znamo, imena objekata znamo — radimo nedvosmisleno usklađenje.

Ponovno zadatak "s zvjezdicom". Uzimamo upit, izvršavamo, nemamo nikakvih aliasa — jednostavno smo dva puta pročitali iz jednog CTE-a.

Gledamo u plan — šta je promašeno? Zašto nam se pojavio alias? Nismo ga naručili. Odakle on takav "numerički"?
PostgreSQL ga sam dodaje. Jednostavno treba razumjeti da takav alias za nas, u svrhu usklađenja s planom, nema nikakvog smisla, jednostavno je ovdje dodat. Nećemo obraćati pažnju na to.
Drugi zadatak "s zvjezdicom": Ako se radi o čitanju iz sekcionirane tablice, dobit ćemo čvor Dodaj ili Spoji Dodaj, koji će se sastojati od velikog broja "djece", a svako od njih će biti nekakav Scan'a iz tablice-sekcije: Sekvencijalno skeniranje, Bitmap Heap Scan ili Index Scan. Ali, u svakom slučaju, ova "djeca" neće biti složeni upiti — tako se ti čvorovi mogu razlikovati od Dodaj u UNION.

Takve čvorove također razumijemo, okupljamo "u jednu hrpu" i kažemo: "sve što si čitao iz megatable — to je ovdje i niz drvo".
"Jednostavni" čvorovi za dobivanje podataka

Skener Vrijednosti u planu odgovara VALUES u upitu.
Rezultat — to je upit bez FROM naizgled SELECT 1. Ili kada imate unaprijed lažan izraz u GDJE-bloku (tada se pojavljuje atribut One-Time Filter):
EXPLAIN ANALYZE
SELECT * FROM pg_class WHERE FALSE; -- ili 0 = 1Rezultat (trošak=0.00..0.00 redovi=0 širina=230) (stvarno vrijeme=0.000..0.000 redovi=0 petlje=1)
One-Time Filter: false
Funkcija skeniranja "mapiraju" se na istoimene SRF.
Ali s umotanim upitima je sve složenije — nažalost, oni ne postaju uvijek InitPlan/SubPlan. Ponekad postaju ... Join ili ... Anti Join, posebno kada napišete nešto kao WHERE NOT EXISTS .... I tamo se ne spajaju uvijek — u tekstu plana nema odgovarajućih čvorova plan operatora.
Ponovno zadatak "s zvjezdicom": nekoliko VALUES u upitu. U tom slučaju, i u planu ćete dobiti nekoliko čvorova Skener Vrijednosti.

Razlikovati ih jedan od drugog pomoći će "numerički" sufiksi — dodaje se upravo u redoslijedu pronalaska odgovarajućih VALUES-blokova tijekom upita odozgo prema dolje.
Obrada podataka
Čini se da smo sve u našem upitu razjasnili — ostao je samo Ograničenje.

Ali ovdje je sve jednostavno — takvi čvorovi kao Ograničenje, Sortiraj, Zbrojno, WindowAgg, Unique "mapiraju" se jedan-na-jedan na odgovarajuće operatore u upitu, ako su tamo. Tu nema nikakvih "zvjezdica" i složenosti.

JOIN
Složenosti nastaju kada želimo povezati JOIN međusobno. To nije uvijek moguće, ali se može.

S gledišta parsera zahtjeva, imamo čvor Pridruži se, koji ima točno dva potomka — lijevog i desnog. To su, redom, ono što je "iznad" vašeg JOIN-a i ono što je "ispod" njega u upitu.
A s gledišta plana, to su dva potomka nekog * Petlja/* Pridruži se-čvora. Ugniježdena petlja, Hash anti JOIN,… — nešto od toga.
Koristit ćemo jednostavnu logiku: ako imamo tablice A i B koje se "spajaju" jedna s drugom u planu, onda su u upitu mogle biti postavljene ili A-JOIN-B, ili B-JOIN-A. Pokušajmo ih spojiti na taj način, zatim na onaj način, i tako sve dok ne dođemo do kraja takvih parova.
Uzmimo naše sintaktičko stablo, uzmimo naš plan, pogledajmo ih… ne izgleda dobro!

Precrtavat ćemo u obliku grafova — oh, sada već izgleda kao nešto!

Obratimo pažnju na to da imamo čvorove koji istovremeno imaju djecu B i C — nije nam važno kojim redoslijedom. Spojit ćemo ih i preokrenuti sliku čvora.

Pogledajmo još jednom. Sada imamo čvorove s djecom A i parom (B + C) — spojimo i njih.

Odlično! Ispostavlja se da smo ta dva JOIN iz upita s čvorovima plana uspješno spojili.
Nažalost, ovaj zadatak ne može se uvijek riješiti.

Na primjer, ako je u upitu A JOIN B JOIN C, a u planu su se prvo povezali "ekstremni" čvorovi A i C. A u upitu nema takvog operatora, nemamo što naglasiti, nemamo za što vezati savjet. Isto tako s "zarezom", kada pišete A, B.
No, u većini slučajeva, gotovo sve čvorove možemo "razviti" i dobiti ovakav profil s lijeve strane po vremenu — doslovno kao u Google Chromeu kada analizirate JavaScript kod. Vidite koliko je vremena svaka linija i svaki operator "izvršavan".

A kako biste lakše koristili sve to, stvorili smo pohranu , gdje možete pohraniti i kasnije pronaći svoje planove zajedno s asociiranim upitima ili podijeliti link s nekim.
Ako vam treba samo pretvoriti nečitljiv upit u razumljiv oblik, iskoristite .

Izvor: habr.com
