PostgreSQL Query Profiler: kako uskladiti plan i upit

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

PostgreSQL Query Profiler: kako uskladiti plan i upit
… u lijepo oblikovan upit s kontekstualnim savjetima o odgovarajućim čvorovima plana:

PostgreSQL Query Profiler: kako uskladiti plan i upit
U ovom razotkrivanju druge polovice svog izlaganja na PGConf.Russia 2020 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 „Recepti za bolesne SQL upite“.


Pogledajte video

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

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

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

Budući da jezgra sustava radi na NodeJS, napravili smo modul za to, možete na GitHubu ga pronaći. 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.
PostgreSQL Query Profiler: kako uskladiti plan i upit

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

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

CTE

Ako se pažljivo pogleda, do 12. verzije (ili počevši od nje s ključnom riječi MATERIALIZED) formiranje CTE je nepobjeđiv barijera za planera.
PostgreSQL Query Profiler: kako uskladiti plan i upit

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

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

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

Ponovno zadatak "s zvjezdicom". Uzimamo upit, izvršavamo, nemamo nikakvih aliasa — jednostavno smo dva puta pročitali iz jednog CTE-a.
PostgreSQL Query Profiler: kako uskladiti plan i upit

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

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

PostgreSQL Query Profiler: kako uskladiti plan i upit

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 = 1

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

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

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

JOIN

Složenosti nastaju kada želimo povezati JOIN međusobno. To nije uvijek moguće, ali se može.
PostgreSQL Query Profiler: kako uskladiti plan i upit

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

Precrtavat ćemo u obliku grafova — oh, sada već izgleda kao nešto!
PostgreSQL Query Profiler: kako uskladiti plan i upit

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

Pogledajmo još jednom. Sada imamo čvorove s djecom A i parom (B + C) — spojimo i njih.
PostgreSQL Query Profiler: kako uskladiti plan i upit

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

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

A kako biste lakše koristili sve to, stvorili smo pohranu arhiva, 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 naš "normalizator".

PostgreSQL Query Profiler: kako uskladiti plan i upit

Izvor: habr.com

Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere 🔥 Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere | ProHoster