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...

PostgreSQL Query Profiler: kako uskladiti plan i upit
... u lijepo dizajniran upit s kontekstualnim savjetima za odgovarajuće čvorove plana:

PostgreSQL Query Profiler: kako uskladiti plan i upit
U ovom prijepisu drugog dijela svoga izvješće na PGConf.Russia 2020 Reći ću vam kako smo to uspjeli.

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.
PostgreSQL Query Profiler: kako uskladiti plan i upit

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.
PostgreSQL Query Profiler: kako uskladiti plan i upit

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.
PostgreSQL Query Profiler: kako uskladiti plan i upit

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.
PostgreSQL Query Profiler: kako uskladiti plan i upit

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.
PostgreSQL Query Profiler: kako uskladiti plan i upit

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.
PostgreSQL Query Profiler: kako uskladiti plan i upit

CTE

Ako pažljivo pogledate, sve do verzije 12 (ili počevši od nje s ključnom riječi MATERIALIZED) formiranje CTE je apsolutna prepreka za planera.
PostgreSQL Query Profiler: kako uskladiti plan i upit

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.
PostgreSQL Query Profiler: kako uskladiti plan i upit
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.
PostgreSQL Query Profiler: kako uskladiti plan i upit

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.
PostgreSQL Query Profiler: kako uskladiti plan i upit

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.
PostgreSQL Query Profiler: kako uskladiti plan i upit

Opet zadatak "sa zvjezdicom". Uzimamo zahtjev, izvršavamo ga, nemamo pseudonime - samo ga dvaput čitamo iz istog CTE-a.
PostgreSQL Query Profiler: kako uskladiti plan i upit

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.
PostgreSQL Query Profiler: kako uskladiti plan i upit

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

PostgreSQL Query Profiler: kako uskladiti plan i upit

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

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 "mapa" na SRF-ove istog imena.

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.
PostgreSQL Query Profiler: kako uskladiti plan i upit

"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.
PostgreSQL Query Profiler: kako uskladiti plan i upit

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.
PostgreSQL Query Profiler: kako uskladiti plan i upit

PRIDRUŽITE

Poteškoće nastaju kada želimo kombinirati JOIN između sebe. To nije uvijek moguće, ali je moguće.
PostgreSQL Query Profiler: kako uskladiti plan i upit

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!
PostgreSQL Query Profiler: kako uskladiti plan i upit

Precrtajmo to u obliku grafikona - oh, već liči na nešto!
PostgreSQL Query Profiler: kako uskladiti plan i upit

Napominjemo da imamo čvorove koji istovremeno imaju djecu B i C - nije nas briga kojim redoslijedom. Kombinirajmo ih i okrenimo sliku čvora.
PostgreSQL Query Profiler: kako uskladiti plan i upit

Pogledajmo ponovno. Sada imamo čvorove s djecom A i parove (B + C) - kompatibilne i s njima.
PostgreSQL Query Profiler: kako uskladiti plan i upit

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.
PostgreSQL Query Profiler: kako uskladiti plan i upit

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.
PostgreSQL Query Profiler: kako uskladiti plan i upit

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".

PostgreSQL Query Profiler: kako uskladiti plan i upit

Izvor: www.habr.com

Dodajte komentar