PostgreSQL Query Profiler: kuidas sobitada plaan ja päring
Paljud, kes juba kasutavad selgitus.tensor.ru - meie PostgreSQL-i plaani visualiseerimisteenus ei pruugi olla teadlik ühest oma ülivõimest - serverilogi raskesti loetava osa keeramisest...
... kaunilt kujundatud päringusse koos kontekstuaalsete vihjetega vastavate plaanisõlmede jaoks:
Esimese osa ärakirja, mis on pühendatud tüüpilistele päringu jõudlusprobleemidele ja nende lahendustele, leiate artiklist "Retseptid haigete SQL-päringute jaoks".
Esiteks alustame värvimist - ja me ei värvi enam plaani, oleme selle juba värvinud, meil on see juba ilus ja arusaadav, aga palve.
Meile tundus, et sellise vormindamata “lehega” näeb palgist tõmmatud taotlus väga kole ja seetõttu ebamugav.
Eriti kui arendajad "liimivad" päringu keha koodi (see on muidugi antimuster, kuid see juhtub) ühele reale. Õudne!
Joonistame selle kuidagi ilusamini.
Ja kui suudame selle ilusti joonistada, st päringu korpuse lahti võtta ja uuesti kokku panna, siis saame igale selle päringu objektile “kinnitada” vihje - mis juhtus plaani vastavas punktis.
Päringu süntaksipuu
Selleks tuleb taotlus esmalt sõeluda.
Sest meil on süsteemi tuum töötab NodeJS-is, siis tegime selle jaoks mooduli, saate leidke see GitHubist. Tegelikult on need laiendatud "sidemed" PostgreSQL-i parseri enda sisemistele. See tähendab, et grammatika kompileeritakse lihtsalt binaarselt ja sellele tehakse NodeJS-ist sidumised. Võtsime aluseks teiste inimeste moodulid – siin pole suurt saladust.
Toidame päringu keha oma funktsiooni sisendiks - väljundis saame parsitud süntaksipuu JSON-objekti kujul.
Nüüd saame selle puu läbida vastupidises suunas ja koostada päringu koos soovitud taande, värvide ja vorminguga. Ei, seda ei saa kohandada, kuid meile tundus, et see oleks mugav.
Päringu ja plaani sõlmede kaardistamine
Nüüd vaatame, kuidas saame ühendada esimeses etapis analüüsitud plaani ja teises etapis analüüsitud päringu.
Võtame lihtsa näite – meil on päring, mis genereerib CTE ja loeb sellest kaks korda. Ta koostab sellise plaani.
See tähendab, et kui me näeme kuskil taotluses CTE generatsiooni ja kuskil plaanis sõlme CTE, siis need sõlmed kindlasti omavahel “kaklevad”, saame need kohe kombineerida.
Probleem tärniga: CTE-sid saab pesastada.
On väga halvasti pesastatud ja isegi samanimelisi. Näiteks saate sees CTE A teha CTE X, ja sees samal tasemel CTE B tee seda uuesti CTE X:
WITH A AS (
WITH X AS (...)
SELECT ...
)
, B AS (
WITH X AS (...)
SELECT ...
)
...
Võrreldes peate sellest aru saama. Selle "silmaga" mõistmine - isegi plaani nägemine, isegi taotluse sisu nägemine - on väga raske. Kui teie CTE põlvkond on keeruline, pesastatud ja taotlused on suured, on see täiesti teadvuseta.
LIIDU
Kui meil on päringus märksõna UNION [ALL] (kahe valimi ühendamise operaator), siis plaanis vastab see kummalegi sõlmele Append, või mõni Recursive Union.
See, mis on ülal "üleval". UNION - see on meie sõlme esimene järeltulija, mis on "all" - teine. Kui läbi UNION meil on siis mitu plokki korraga “liimitud”. Append-sõlm on endiselt ainult üks, kuid sellel pole mitte kaks, vaid palju lapsi - vastavalt nende liikumise järjekorras:
(...) -- #1
UNION ALL
(...) -- #2
UNION ALL
(...) -- #3
Append
-> ... #1
-> ... #2
-> ... #3
Probleem tärniga: rekursiivse valimi genereerimise sees (WITH RECURSIVE) võib olla ka rohkem kui üks UNION. Kuid ainult kõige viimane plokk pärast viimast on alati rekursiivne UNION. Kõik ülaltoodud on üks, kuid erinev UNION:
WITH RECURSIVE T AS(
(...) -- #1
UNION ALL
(...) -- #2, тут кончается генерация стартового состояния рекурсии
UNION ALL
(...) -- #3, только этот блок рекурсивный и может содержать обращение к T
)
...
Samuti peate suutma selliseid näiteid "välja tõmmata". Selles näites näeme seda UNION-meie taotluses oli 3 segmenti. Vastavalt sellele üks UNION vastab Append-sõlm ja teisele - Recursive Union.
Andmete lugemine-kirjutamine
Kõik on paika pandud, nüüd teame, milline osa taotlusest vastab millisele plaaniosale. Ja nendest tükkidest leiame lihtsalt ja loomulikult need objektid, mis on "loetavad".
Päringu seisukohast ei tea me, kas see on tabel või CTE, kuid neid määrab sama sõlm RangeVar. Ja "loetavuse" osas on see ka üsna piiratud sõlmede komplekt:
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]
Teame plaani ja päringu ülesehitust, teame plokkide vastavust, teame objektide nimetusi - teeme üks-ühele võrdluse.
Jällegi ülesanne "tärniga". Võtame päringu vastu, täidame selle, meil pole varjunimesid – lugesime seda lihtsalt kaks korda samast CTE-st.
Vaatame plaani – milles probleem? Miks meil oli alias? Me ei tellinud seda. Kust ta sellise “numbrinumbri” võtab?
PostgreSQL lisab selle ise. Peate lihtsalt sellest aru saama just selline alias meie jaoks planeeringuga võrdlemise eesmärgil pole sellel mingit mõtet, see on lihtsalt siia lisatud. Ärme pööra talle tähelepanu.
Teine ülesanne "tärniga": kui loeme partitsioonidega tabelist, siis saame sõlme Append või Merge Append, mis koosneb suurest hulgast “lastest” ja millest igaüks on kuidagi Scan'om tabeliosast: Seq Scan, Bitmap Heap Scan või Index Scan. Kuid igal juhul ei saa need "lapsed" olla keerulised päringud - nii saab neid sõlmi eristada Append juures UNION.
Mõistame ka selliseid sõlmi, kogume need "ühte hunnikusse" ja ütleme: "kõik, mida megatable’ist loed, on siin ja puu otsas".
"Lihtsad" andmete vastuvõtmise sõlmed
Values Scan vastab plaanile VALUES taotluses.
Result on taotlus ilma FROM meeldib SELECT 1. Või kui teil on tahtlikult vale väljend WHERE-block (siis ilmub atribuut One-Time Filter):
EXPLAIN ANALYZE
SELECT * FROM pg_class WHERE FALSE; -- или 0 = 1
Kuid pesastatud päringutega on kõik keerulisem - kahjuks ei muutu need alati nendeks InitPlan/SubPlan. Mõnikord muutuvad need ... Join või ... Anti Join, eriti kui kirjutad midagi taolist WHERE NOT EXISTS .... Ja siin pole neid alati võimalik kombineerida - plaani tekstis pole plaani sõlmedele vastavaid operaatoreid.
Jällegi ülesanne "tärniga": mõned VALUES taotluses. Sel juhul ja plaanis saate mitu sõlme Values Scan.
"Nummerdatud" järelliited aitavad neid üksteisest eristada - need lisatakse täpselt vastavate leidmise järjekorras VALUES-blokeerib päringu ülalt alla.
Andmetöötlus
Näib, et kõik meie taotluses on lahendatud – jäänud on vaid Limit.
Kuid siin on kõik lihtne - sellised sõlmed nagu Limit, Sort, Aggregate, WindowAgg, Unique "kaardistada" üks-ühele päringu vastavate operaatoritega, kui need on olemas. Siin pole "tähti" ega raskusi.
LIITU
Raskused tekivad siis, kui tahame kombineerida JOIN omavahel. See ei ole alati võimalik, kuid see on võimalik.
Päringu parseri seisukohast on meil sõlm JoinExpr, millel on täpselt kaks last – vasak ja parem. See on seega see, mis on teie JOIN-i kohal ja mis on taotluses kirjutatud selle alla.
Ja planeeringu seisukohalt on tegemist mõne kahe järeltulijaga * Loop/* Join-sõlm. Nested Loop, Hash Anti Join,... - midagi sellist.
Kasutame lihtsat loogikat: kui meil on tabelid A ja B, mis plaanis teineteisega “liituvad”, siis päringus võiksid need paikneda kas A-JOIN-BVõi B-JOIN-A. Proovime kombineerida nii, proovime kombineerida teistpidi ja nii edasi, kuni sellised paarid saavad otsa.
Võtame oma süntaksipuu, võtame oma plaani, vaatame neid... mitte sarnased!
Joonistame selle graafikute kujul ümber – oi, see tundub juba midagi!
Pangem tähele, et meil on sõlmed, millel on samaaegselt lapsed B ja C – meid ei huvita, mis järjekorras. Kombineerime need ja pöörame sõlme pildi ümber.
Vaatame uuesti. Nüüd on meil sõlmed lastega A ja paaridega (B + C) - ühilduvad ka nendega.
Suurepärane! Selgub, et me oleme need kaks JOIN taotlusest koos plaani sõlmedega ühendati edukalt.
Kahjuks seda probleemi alati ei lahendata.
Näiteks kui taotluses A JOIN B JOIN C, ja plaanis ühendati ennekõike “välimised” sõlmed A ja C. Aga sellist operaatorit taotluses pole, meil pole midagi esile tõsta, millele vihjet lisada. Sama on "komaga" kirjutamisel A, B.
Kuid enamikul juhtudel saab peaaegu kõik sõlmed "lahti siduda" ja sellist profiili saate õigeaegselt vasakule teha - sõna otseses mõttes nagu Google Chrome'is JavaScripti koodi analüüsimisel. Näete, kui kaua kulus iga rea ja iga avalduse täitmiseks.
Ja selleks, et teil oleks seda kõike mugavam kasutada, oleme teinud hoiustamise arhiiv, kus saate salvestada ja hiljem leida oma plaanid koos seotud taotlustega või jagada linki kellegagi.
Kui teil on vaja lihtsalt loetamatu päring viia sobivasse vormi, kasutage meie "normalisaator".