ProHoster > Blog > uprava > PostgreSQL Query Profiler: kako uskladiti plan i upit
PostgreSQL Query Profiler: kako uskladiti plan i upit
Mnogi koji već koriste objasniti.tensor.ru - naša usluga vizualizacije plana PostgreSQL možda nije svjesna jedne od svojih supermoći - okretanje teško čitljivog dijela dnevnika poslužitelja...
... u lijepo dizajniran upit s kontekstualnim savjetima za odgovarajuće čvorove plana:
Transkript prvog dijela, posvećen tipičnim problemima izvedbe upita i njihovim rješenjima, možete pronaći u članku "Recepti za bolesne SQL upite".
Prvo, počnimo bojati - i više nećemo bojati plan, već smo ga obojali, već ga imamo lijepog i razumljivog, ali zahtjev.
Činilo nam se da s tako neformatiranim "listom" zahtjev izvučen iz dnevnika izgleda vrlo ružno i stoga nezgodno.
Pogotovo kada programeri "zalijepe" tijelo zahtjeva u kodu (ovo je, naravno, antiuzorak, ali događa se) u jednom retku. Užasno!
Nacrtajmo ovo nekako ljepše.
A ako ovo možemo lijepo nacrtati, to jest rastaviti i ponovno sastaviti tijelo zahtjeva, tada možemo svakom objektu ovog zahtjeva "priložiti" nagovještaj - što se dogodilo na odgovarajućoj točki u planu.
Stablo sintakse upita
Da biste to učinili, zahtjev se prvo mora analizirati.
Jer imamo jezgra sustava radi na NodeJS, onda smo napravili modul za to, možete pronađite na GitHubu. Zapravo, ovo su proširena "vezivanja" na unutarnje dijelove samog PostgreSQL parsera. Odnosno, gramatika je jednostavno binarno kompajlirana i na nju se povezuju iz NodeJS-a. Za osnovu smo uzeli module drugih ljudi - tu nema velike tajne.
Unosimo tijelo zahtjeva kao ulaz u našu funkciju - na izlazu dobivamo raščlanjeno stablo sintakse u obliku JSON objekta.
Sada možemo proći kroz ovo stablo u suprotnom smjeru i sastaviti zahtjev s uvlakama, bojom i oblikovanjem koje želimo. Ne, ovo nije prilagodljivo, ali činilo nam se da bi bilo zgodno.
Mapiranje čvorova upita i plana
Sada da vidimo kako možemo kombinirati plan koji smo analizirali u prvom koraku i upit koji smo analizirali u drugom.
Uzmimo jednostavan primjer - imamo upit koji generira CTE i dvaput ga čita. On stvara takav plan.
To znači da ako vidimo CTE generaciju negdje u zahtjevu i čvor negdje u planu CTE, onda se ovi čvorovi definitivno "bore" jedni s drugima, možemo ih odmah kombinirati.
Problem sa zvjezdicom: CTE se mogu ugniježditi.
Ima ih vrlo loše ugniježđenih, pa čak i istoimenih. Na primjer, možete unutra CTE A napraviti CTE X, a na istoj razini unutra CTE B ponovi to CTE X:
WITH A AS (
WITH X AS (...)
SELECT ...
)
, B AS (
WITH X AS (...)
SELECT ...
)
...
Kada uspoređujete, ovo morate razumjeti. Razumjeti ovo "svojim očima" - čak i vidjeti plan, čak i vidjeti tijelo zahtjeva - vrlo je teško. Ako je vaša CTE generacija složena, ugniježđena i zahtjevi su veliki, onda je to potpuno nesvjesno.
UNIJA
Ako u upitu imamo ključnu riječ UNION [ALL] (operator spajanja dva uzorka), tada u planu odgovara bilo kojem čvoru Append, ili neke Recursive Union.
Ono što je "iznad" gore UNION - ovo je prvi potomak našeg čvora, koji je "ispod" - drugi. Ako kroz UNION imamo nekoliko blokova "zalijepljenih" odjednom, dakle Append- i dalje će postojati samo jedan čvor, ali neće imati dva, već mnogo djece - redoslijedom kojim idu:
(...) -- #1
UNION ALL
(...) -- #2
UNION ALL
(...) -- #3
Append
-> ... #1
-> ... #2
-> ... #3
Problem sa zvjezdicom: unutar rekurzivnog generiranja uzorkovanja (WITH RECURSIVE) također može biti više od jednog UNION. Ali samo posljednji blok nakon posljednjeg uvijek je rekurzivan UNION. Sve gore je jedno, ali različito UNION:
WITH RECURSIVE T AS(
(...) -- #1
UNION ALL
(...) -- #2, тут кончается генерация стартового состояния рекурсии
UNION ALL
(...) -- #3, только этот блок рекурсивный и может содержать обращение к T
)
...
Takve primjere također morate znati "istaknuti". U ovom primjeru to vidimo UNION-u našem zahtjevu bila su 3 segmenta. Sukladno tome, jedan UNION odgovara Append-čvor, a na drugu - Recursive Union.
Podaci za čitanje i pisanje
Sve je postavljeno, sada znamo koji dio zahtjeva odgovara kojem dijelu plana. I u tim komadima lako i prirodno možemo pronaći one objekte koji su “čitljivi”.
Sa stajališta upita, ne znamo je li to tablica ili CTE, ali označeni su istim čvorom RangeVar. A u smislu "čitljivosti", ovo je također prilično ograničen skup čvorova:
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]
Znamo strukturu plana i upit, znamo korespondenciju blokova, znamo nazive objekata - radimo usporedbu jedan na jedan.
Opet zadatak "sa zvjezdicom". Uzimamo zahtjev, izvršavamo ga, nemamo pseudonime - samo ga dvaput čitamo iz istog CTE-a.
Gledamo plan - u čemu je problem? Zašto smo imali alias? Nismo naručili. Odakle mu takav "broj"?
PostgreSQL ga sam dodaje. Samo to trebate razumjeti upravo takav alias nama za potrebe usporedbe s planom nema nikakvog smisla, jednostavno je ovdje dodano. Ne obazirimo se na njega.
Drugi zadatak "sa zvjezdicom": ako čitamo iz particionirane tablice, tada ćemo dobiti čvor Append ili Merge Append, koja će se sastojati od velikog broja “djece”, a od kojih će svako biti nekako Scan'om iz odjeljka tablice: Seq Scan, Bitmap Heap Scan ili Index Scan. No, u svakom slučaju, ta "djeca" neće biti složeni upiti - po tome se ti čvorovi mogu razlikovati od Append na UNION.
Razumijemo i takve čvorove, sakupimo ih "na jednu hrpu" i kažemo: "sve što čitate s megatablea je ovdje i dolje".
"Jednostavni" čvorovi za primanje podataka
Values Scan planski odgovara VALUES u zahtjevu.
Result je zahtjev bez FROM vrsta SELECT 1. Ili kada imate namjerno lažan izraz WHERE-blok (tada se pojavljuje atribut One-Time Filter):
EXPLAIN ANALYZE
SELECT * FROM pg_class WHERE FALSE; -- или 0 = 1
Ali s ugniježđenim upitima sve je kompliciranije - nažalost, oni se ne pretvaraju uvijek u InitPlan/SubPlan. Ponekad se pretvore u ... Join ili ... Anti Join, pogotovo kada pišete nešto poput WHERE NOT EXISTS .... I ovdje ih nije uvijek moguće kombinirati - u tekstu plana nema operatora koji odgovaraju čvorovima plana.
Opet zadatak "sa zvjezdicom": neki VALUES u zahtjevu. U ovom slučaju iu planu dobit ćete nekoliko čvorova Values Scan.
"Numerirani" sufiksi pomoći će da ih razlikujete jedan od drugog - dodaju se točno onim redoslijedom kojim su pronađeni odgovarajući VALUES-blokovi duž zahtjeva od vrha do dna.
Obrada podataka
Čini se da je sve u našem zahtjevu sređeno - sve što je ostalo je Limit.
Ali ovdje je sve jednostavno - takvi čvorovi kao Limit, Sort, Aggregate, WindowAgg, Unique “preslikati” jedan na jedan na odgovarajuće operatore u zahtjevu, ako postoje. Ovdje nema "zvijezda" niti poteškoća.
PRIDRUŽITE
Poteškoće nastaju kada želimo kombinirati JOIN između sebe. To nije uvijek moguće, ali je moguće.
Sa stajališta analizatora upita, imamo čvor JoinExpr, koja ima točno dvoje djece – lijevo i desno. Ovo je, prema tome, ono što je "iznad" vašeg JOIN-a i ono što je napisano "ispod" njega u zahtjevu.
A s gledišta plana, to su dva potomka nekih * Loop/* Join-čvor. Nested Loop, Hash Anti Join,... - nešto kao to.
Poslužimo se jednostavnom logikom: ako imamo tablice A i B koje se međusobno "pridružuju" u planu, tada bi se u zahtjevu mogle nalaziti ili A-JOIN-BIli B-JOIN-A. Probajmo kombinirati ovako, probajmo obrnuto i tako dok nam ne ponestane takvih parova.
Uzmimo naše stablo sintakse, naš plan, pogledajmo ih... nisu slični!
Precrtajmo to u obliku grafikona - oh, već liči na nešto!
Napominjemo da imamo čvorove koji istovremeno imaju djecu B i C - nije nas briga kojim redoslijedom. Kombinirajmo ih i okrenimo sliku čvora.
Pogledajmo ponovno. Sada imamo čvorove s djecom A i parove (B + C) - kompatibilne i s njima.
Sjajno! Ispada da smo mi ovo dvoje JOIN iz zahtjeva s planom čvorovi su uspješno kombinirani.
Nažalost, ovaj problem nije uvijek riješen.
Na primjer, ako u zahtjevu A JOIN B JOIN C, a u planu su prije svega povezani "vanjski" čvorovi A i C. Ali u zahtjevu nema takvog operatora, nemamo što istaknuti, ništa čemu priložiti savjet. Isto je i sa "zarezom" kad pišeš A, B.
Ali, u većini slučajeva, gotovo svi čvorovi se mogu "odvezati" i možete dobiti ovu vrstu profiliranja s lijeve strane na vrijeme - doslovno, kao u Google Chromeu kada analizirate JavaScript kod. Možete vidjeti koliko je vremena trebalo "izvršiti" svakom redu i svakoj izjavi.
A kako bismo vam sve to učinili praktičnijim, napravili smo spremište arhiva, gdje možete spremiti i kasnije pronaći svoje planove zajedno s povezanim zahtjevima ili podijeliti vezu s nekim.
Ako samo trebate dovesti nečitljiv upit u adekvatan oblik, koristite naš "normalizator".