ProHoster > Blog > Uprava > PostgreSQL Query Profiler: kako uskladiti načrt in poizvedbo
PostgreSQL Query Profiler: kako uskladiti načrt in poizvedbo
Mnogi, ki že uporabljajo expand.tensor.ru - naša storitev za vizualizacijo načrta PostgreSQL se morda ne zaveda ene od svojih velemoči - obračanje težko berljivega dela strežniškega dnevnika ...
... v lepo oblikovano poizvedbo s kontekstualnimi namigi za ustrezna vozlišča načrta:
Prepis prvega dela, posvečenega tipičnim težavam pri izvajanju poizvedb in njihovim rešitvam, najdete v članku "Recepti za slabe poizvedbe SQL".
Najprej začnimo z barvanjem - in ne bomo več barvali načrta, že smo ga pobarvali, že imamo lepo in razumljivo, ampak zahtevo.
Zdelo se nam je, da je zahteva, izvlečena iz dnevnika z neformatiranim "listom", videti zelo grda in zato neprijetna.
Še posebej, ko razvijalci "zlepijo" telo zahteve v kodi (to je seveda antipattern, vendar se zgodi) v eno vrstico. Grozno!
Narišimo to nekako lepše.
In če lahko to lepo narišemo, torej razstavimo in ponovno sestavimo telo zahteve, potem lahko vsakemu predmetu te zahteve »pripnemo« namig - kaj se je zgodilo na ustrezni točki v načrtu.
Drevo sintakse poizvedbe
Za to je treba zahtevo najprej razčleniti.
Ker imamo jedro sistema deluje na NodeJS, potem smo naredili modul za to, lahko poiščite na GitHubu. Pravzaprav so to razširjene "povezave" na notranjost samega razčlenjevalnika PostgreSQL. To pomeni, da je slovnica preprosto binarno prevedena in nanjo so narejene vezi iz NodeJS. Za osnovo smo vzeli module drugih ljudi - tukaj ni velike skrivnosti.
Telo zahteve podajamo kot vhod v našo funkcijo - na izhodu dobimo razčlenjeno skladenjsko drevo v obliki objekta JSON.
Zdaj lahko gremo skozi to drevo v nasprotni smeri in sestavimo zahtevo z zamiki, barvami in oblikovanjem, ki jih želimo. Ne, tega ni mogoče prilagoditi, vendar se nam je zdelo, da bi bilo to priročno.
Preslikava vozlišč poizvedbe in načrta
Zdaj pa poglejmo, kako lahko združimo načrt, ki smo ga analizirali v prvem koraku, in poizvedbo, ki smo jo analizirali v drugem.
Vzemimo preprost primer – imamo poizvedbo, ki ustvari CTE in ga dvakrat prebere. On ustvari tak načrt.
To pomeni, da če vidimo generacijo CTE nekje v zahtevi in vozlišče nekje v načrtu CTE, potem se ta vozlišča med seboj zagotovo »bojijo«, jih lahko takoj združimo.
Težava z zvezdico: CTE je mogoče ugnezditi.
Obstajajo zelo slabo ugnezdeni in celo istoimenski. Na primer, lahko notri CTE A naredi CTE X, in na isti ravni znotraj CTE B naredi še enkrat CTE X:
WITH A AS (
WITH X AS (...)
SELECT ...
)
, B AS (
WITH X AS (...)
SELECT ...
)
...
Ko primerjate, morate to razumeti. Razumeti to "z očmi" - celo videti načrt, celo videti telo zahteve - je zelo težko. Če je vaša generacija CTE zapletena, ugnezdena in so zahteve velike, potem je popolnoma nezavedna.
UNION
Če imamo v poizvedbi ključno besedo UNION [ALL] (operator združevanja dveh vzorcev), potem v načrtu ustreza bodisi vozlišču Append, ali nekaj Recursive Union.
Tisto, kar je "zgoraj" zgoraj UNION - to je prvi potomec našega vozlišča, ki je "spodaj" - drugi. Če skozi UNION imamo več blokov "zlepljenih" naenkrat, torej Append- še vedno bo samo eno vozlišče, vendar ne bo imelo dveh, ampak veliko otrok - v vrstnem redu, v katerem gredo:
(...) -- #1
UNION ALL
(...) -- #2
UNION ALL
(...) -- #3
Append
-> ... #1
-> ... #2
-> ... #3
Težava z zvezdico: znotraj generiranja rekurzivnega vzorčenja (WITH RECURSIVE) je lahko tudi več kot ena UNION. Toda samo zadnji blok za zadnjim je vedno rekurziven UNION. Vse zgoraj je eno, a različno UNION:
WITH RECURSIVE T AS(
(...) -- #1
UNION ALL
(...) -- #2, тут кончается генерация стартового состояния рекурсии
UNION ALL
(...) -- #3, только этот блок рекурсивный и может содержать обращение к T
)
...
Takšne primere moraš znati tudi »štrleti«. V tem primeru to vidimo UNION-v naši zahtevi so bili 3 segmenti. V skladu s tem ena UNION соответствующий Append-vozlišče, in na drugo - Recursive Union.
Podatki za branje in pisanje
Vse je postavljeno, zdaj vemo, kateri del zahteve ustreza kateremu delu načrta. In v teh delih zlahka in naravno najdemo tiste predmete, ki so »berljivi«.
Z vidika poizvedbe ne vemo, ali gre za tabelo ali CTE, vendar ju določa isto vozlišče RangeVar. Kar zadeva "berljivost", je to tudi dokaj omejen nabor vozlišč:
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]
Poznamo strukturo načrta in poizvedbe, poznamo korespondenco blokov, poznamo imena objektov - naredimo primerjavo ena proti ena.
Ponovno naloga "z zvezdico". Sprejmemo zahtevo, jo izvedemo, nimamo vzdevkov - samo dvakrat jo preberemo iz istega CTE.
Pogledamo načrt - v čem je problem? Zakaj smo imeli vzdevek? Nismo ga naročili. Kje dobi takšno "številko"?
PostgreSQL ga doda sam. Samo to moraš razumeti samo tak vzdevek za nas za primerjavo z načrtom nima smisla, tukaj je preprosto dodana. Ne bodimo pozorni nanj.
2. naloga "z zvezdico": če beremo iz particionirane tabele, bomo dobili vozlišče Append ali Merge Append, ki bo sestavljen iz velikega števila »otrok«, od katerih bo vsak nekako Scan'om iz razdelka tabele: Seq Scan, Bitmap Heap Scan ali Index Scan. Toda v vsakem primeru ti "otroci" ne bodo zapletene poizvedbe - tako je mogoče ta vozlišča razlikovati od Append na UNION.
Razumemo tudi takšne vozle, jih zberemo "na en kup" in rečemo: "vse, kar preberete iz megatable, je tukaj in spodaj".
"Enostavna" vozlišča za sprejem podatkov
Values Scan ustreza načrtu VALUES v zahtevi.
Result je prošnja brez FROM nekako SELECT 1. Ali ko imate namenoma lažen izraz WHERE-block (takrat se pojavi atribut One-Time Filter):
EXPLAIN ANALYZE
SELECT * FROM pg_class WHERE FALSE; -- или 0 = 1
Toda z ugnezdenimi poizvedbami je vse bolj zapleteno - na žalost se ne spremenijo vedno v InitPlan/SubPlan. Včasih se spremenijo v ... Join ali ... Anti Join, sploh ko napišeš kaj takega WHERE NOT EXISTS .... In tukaj jih ni vedno mogoče združiti - v besedilu načrta ni operaterjev, ki bi ustrezali vozliščem načrta.
Ponovno naloga "z zvezdico": nekaj VALUES v zahtevi. V tem primeru in v načrtu boste dobili več vozlišč Values Scan.
"Oštevilčene" pripone jih bodo pomagale razlikovati med seboj - dodane so točno v vrstnem redu, v katerem so najdene ustrezne VALUES-bloki vzdolž zahteve od zgoraj navzdol.
Obdelava podatkov
Zdi se, da je bilo vse v naši zahtevi urejeno - vse, kar je ostalo, je Limit.
Ampak tukaj je vse preprosto - takšna vozlišča kot Limit, Sort, Aggregate, WindowAgg, Unique »preslikati« ena proti ena na ustrezne operaterje v zahtevi, če so tam. Tu ni "zvezd" ali težav.
PRIDRUŽITE
Težave nastanejo, ko želimo kombinirati JOIN med sabo. To ni vedno mogoče, je pa mogoče.
Z vidika razčlenjevalnika poizvedb imamo vozlišče JoinExpr, ki ima točno dva otroka – levega in desnega. To je torej tisto, kar je "nad" vašim JOIN-om in tisto, kar je v zahtevi zapisano "pod" njim.
In z vidika načrta sta to dva potomca nekaterih * Loop/* Join-vozlišče. Nested Loop, Hash Anti Join,... - nekaj takega.
Uporabimo preprosto logiko: če imamo tabeli A in B, ki se v načrtu "združita", potem sta v zahtevi lahko locirani bodisi A-JOIN-BAli B-JOIN-A. Poskusimo kombinirati tako, poskusimo kombinirati obratno in tako naprej, dokler nam takih parov ne zmanjka.
Vzemimo naše skladenjsko drevo, vzemimo naš načrt, poglejmo jih ... niso podobni!
Prerišimo ga v obliki grafov - oh, že nekaj izgleda!
Upoštevajmo, da imamo vozlišča, ki imajo istočasno otroka B in C – vseeno nam je, v kakšnem vrstnem redu. Združimo jih in obrnemo sliko vozlišča.
Poglejmo še enkrat. Zdaj imamo vozlišča z otroki A in pari (B + C) – združljiva tudi z njimi.
Super! Izkazalo se je, da sva ta dva JOIN iz zahteve z vozlišči načrta so bila uspešno združena.
Žal, ta problem ni vedno rešen.
Na primer, če v zahtevi A JOIN B JOIN C, in v načrtu sta bila najprej povezana »zunanja« vozlišča A in C. Toda v zahtevi ni takega operaterja, nimamo česa poudariti, česa priložiti namig. Enako je z "vejico", ko pišeš A, B.
Toda v večini primerov je skoraj vsa vozlišča mogoče "odvezati" in pravočasno pridobiti tovrstno profiliranje na levi - dobesedno, kot v Google Chromu, ko analizirate kodo JavaScript. Vidite lahko, koliko časa je trajalo, da se je »izvedla« vsaka vrstica in vsak stavek.
In da bi vam olajšali uporabo vsega tega, smo naredili shrambo arhiv, kjer lahko shranite in pozneje najdete svoje načrte skupaj s povezanimi zahtevami ali delite povezavo z nekom.
Če morate samo neberljivo poizvedbo spraviti v ustrezno obliko, uporabite naš "normalizator".