MĂ„nga som redan anvĂ€nder â vĂ„r tjĂ€nst för visualisering av PostgreSQL-planer Ă€r kanske inte medvetna om en av dess superkrafter â att förvandla en svĂ„rlĂ€st loggbit frĂ„n servernâŠ

⊠till en vackert formaterad frÄga med kontextuella förslag för de relevanta plan-noderna:

I denna transkription av den andra delen av min ska jag berÀtta hur vi lyckades med detta.
Transkriptet av den första delen, som handlar om typiska prestandaproblem med frÄgor och deras lösningar, kan lÀsas i artikeln .

LĂ„t oss börja med fĂ€rglĂ€ggningen â och vi kommer inte att fĂ€rglĂ€gga planen, den har vi redan gjort, den Ă€r redan vacker och förstĂ„elig, utan frĂ„gan.
Vi tyckte att den hĂ€r oformaterade "lakanet" som vi plockade ut ur loggen sĂ„g hemskt ut och dĂ€rför â var obekvĂ€m.

Speciellt nÀr utvecklarna klipper in frÄgekroppen i en rad i koden (detta Àr givetvis ett antipattern, men det hÀnder). Usch!
LÄt oss rita detta pÄ ett mer estetiskt sÀtt.

Och om vi kan rita det vackert, det vill sĂ€ga bryta ner och sĂ€tta ihop frĂ„gekroppen igen, sĂ„ kan vi sedan koppla varje objekt i denna frĂ„ga med ett förslag â vad som hĂ€nde vid den motsvarande punkten i planen.
SyntaxtrÀdet för frÄgan
För att göra detta mÄste frÄgan först analyseras.

Eftersom , sĂ„ har vi gjort ett litet modul till det, ni kan . I sjĂ€lva verket Ă€r detta utökade "bindningar" till de interna delarna av PostgreSQL:s parser. Det vill sĂ€ga, en grammatik som bara har kompilerats binĂ€rt och bindningar har gjorts frĂ„n NodeJS-sidan. Vi utgick frĂ„n andra moduler â det finns ingen stor hemlighet hĂ€r.
Vi matar in frĂ„gekroppen i vĂ„r funktion â vi fĂ„r ut det analyserade syntaxtrĂ€det i form av ett JSON-objekt.

Nu kan vi gÄ igenom detta trÀd bakÄt och sammanstÀlla frÄgan med de indrag, fÀrglÀggning och formatering som vi vill ha. Nej, det kan inte anpassas, men vi tyckte att det skulle vara bekvÀmt pÄ detta sÀtt.

Kopplingen av frÄgans och planens noder
Nu ska vi se hur vi kan kombinera planen som vi analyserade i första steget med frÄgan som vi analyserade i det andra.
LĂ„t oss ta ett enkelt exempel â vi har en frĂ„ga som formar en CTE och lĂ€ser frĂ„n den tvĂ„ gĂ„nger. Den genererar en sĂ„dan plan.

CTE
Om man ser noga pÄ den, vad som gÀller frÄn och med version 12 (eller snarare frÄn den med nyckelordet MATERIALIZED) formar .

Det betyder att om vi ser nÄgonstans i frÄgan generering av CTE och nÄgonstans i planen en nod CTE, sÄ Àr dessa noder tydligt "krockande", vi kan genast kombinera dem.
Uppgift med en stjÀrna: CTE kan vara nÀstlade.

Det kan finnas mycket dÄligt nÀstlade, och till och med med samma namn. Till exempel kan du inuti CTE A göra CTE X, och pÄ samma nivÄ inuti CTE B göra Äterigen CTE X:
WITH A AS (
WITH X AS (...)
SELECT ...
)
, B AS (
WITH X AS (...)
SELECT ...
)
...Vid mappning mĂ„ste du förstĂ„ detta. Att förstĂ„ detta "med ögonen" â Ă€ven se planen, Ă€ven se kropp av frĂ„gan â Ă€r mycket svĂ„rt. Om du har en komplex, nĂ€stlad generering av CTE, stora frĂ„gor â dĂ„ Ă€r det helt omedvetet.
UNION
Om vi i frÄgan har nyckelordet UNION [ALL] (operator för att förena tvÄ urval), sÄ motsvarar det i planen antingen en nod LÀgg till, eller nÄgon Rekursiv förening.

Det som "övanför" Ă€r den första avkomman till vĂ„r nod, och vad "under" Ă€r den andra. Om genom UNION har vi "klistrat" flera block samtidigt, sĂ„ UNION -noden kommer att vara bara en, men dess barn kommer inte att vara tvĂ„, utan mĂ„nga â i ordning som de kommer, enligt: LĂ€gg till(...) -- #1 UNION ALL (...) -- #2 UNION ALL (...) -- #3
Append
-> ... #1
-> ... #2
-> ... #3: inuti generering av rekursivt urval (
Uppgift med en stjĂ€rna) kan det ocksĂ„ vara mer Ă€n enWITH RECURSIVE. Men alltid Ă€r endast det allra sista blocket efter den sista UNION. Allt ovanför â det Ă€r en, men en annan UNIONWITH RECURSIVE T AS( (...) -- #1 UNION ALL (...) -- #2, hĂ€r avslutas genereringen av det initiala tillstĂ„ndet för rekursionen UNION ALL (...) -- #3, endast detta block Ă€r rekursivt och kan innehĂ„lla referens till T ) ... UNION:
SĂ„dana exempel mĂ„ste man ocksĂ„ kunna "klistra upp". HĂ€r ser vi i detta exempel att -det fanns 3 stycken segment i vĂ„r frĂ„ga. DĂ€rmed, en UNION-nod, och den andra â UNION motsvarar LĂ€gg tillLĂ€sa-skriva data Rekursiv förening.

Allt, vi har delat upp det, nu vet vi vilken del av frÄgan som motsvarar vilken del av planen. Och i dessa delar kan vi enkelt och obehindrat hitta de objekt som " lÀses".
FrĂ„n frĂ„gans synvinkel vet vi inte â om det Ă€r en tabell eller CTE, men de betecknas med samma nod.
C Utbud. Och i planen "lÀser" detta ocksÄ, en ganska begrÀnsad uppsÀttning noder.
Sekvensskanning pÄ [tbl]Bitmap Heap Scan pÄ [tbl]Index [Only] Skanna [BakÄt] anvÀnder [idx] pÄ [tbl]CTE-skanning pÄ [cte]SÀtt in/Uppdatering/Databas trÀd pÄ [tbl]
Vi kÀnner till strukturen av planen och förfrÄgan, vi vet överensstÀmmelsen av blocken, vi vet namnen pÄ objekten - vi gör en entydig matchning.

Ă
terigen uppgiften "med stjÀrna". Vi tar förfrÄgan, utför den, vi har inga alias - vi lÀste bara tvÄ gÄnger frÄn samma CTE.

Vi tittar pÄ planen - vad Àr problemet? Varför dyker vÄrt alias upp? Vi bestÀllde inte det. Var kommer det hÀr "nummerliknande" ifrÄn?
PostgreSQL lÀgger till det automatiskt. Man mÄste helt enkelt förstÄ att just ett sÄdant alias för oss, i syfte att matcha med planen, har ingen mening, det har bara lagts till hÀr. Vi kommer inte att bry oss om det.
Andra uppgiften "med stjÀrna": om vi lÀser frÄn en partitionerad tabell, fÄr vi en nod LÀgg till eller Sammanfoga lÀgg till, som kommer att bestÄ av ett stort antal "barn", och varje av dem kommer att vara nÄgot Scan'frÄn tabellsektionen: Sekvensskanning, Bitmap Heap Scan eller Index Scan. Men oavsett vad, dessa "barn" kommer inte att vara komplicerade förfrÄgningar - sÄ dessa noder kan sÀrskiljas frÄn LÀgg till nÀr UNION.

SÄdana noder förstÄr vi ocksÄ, vi samlar "i en hög" och sÀger: "allt som du har lÀst frÄn megatables - det Àr hÀr och ner i trÀdet".
"Enkla" noder för datainhÀmtning

VÀrdeskanning i planen motsvarar VALUES i förfrÄgan.
Resultat â det Ă€r en förfrĂ„gan utan FRĂ
N sĂ„dana som SELECT 1. Eller nĂ€r du har ett medvetet falskt uttryck i DĂR-block (dĂ„ kommer attributet One-Time Filter):
EXPLAIN ANALYZE
SELECT * FROM pg_class WHERE FALSE; -- eller 0 = 1Resultat (kostnad=0.00..0.00 rader=0 bredd=230) (faktisk tid=0.000..0.000 rader=0 loop=1)
One-Time Filter: false
Funktion Skanna "mappas" till namngivna SRF.
Men med inbÀddade förfrÄgningar Àr det svÄrare - tyvÀrr omvandlas de inte alltid till InitPlan/Underplan. Ibland omvandlas de till ... Join eller ... Anti Join, speciellt nÀr du skriver nÄgot som WHERE NOT EXISTS .... Och dÀr gÄr det inte alltid att kombinera - i planen finns inga motsvarande operatörer.
Ă
terigen uppgiften "med stjÀrna": flera VALUES i förfrÄgan. I det fallet fÄr du flera noder i planen. VÀrdeskanning.

Att sÀrskilja dem frÄn varandra kan hjÀlpa "nummer" suffixen - det lÀggs precis till i ordning för att hitta motsvarande VALUES-block under förfrÄgan uppifrÄn och ner.
Databehandling
Det verkar som att allt Àr klart i vÄr förfrÄgan - det ÄterstÄr bara BegrÀnsa.

Men hÀr Àr det enkelt - sÄdana noder som BegrÀnsa, Sortera, Samla, WindowAgg, Unique "mappas" en-till-en med motsvarande operatörer i förfrÄgan, om de finns. HÀr finns inga "stjÀrnor" eller svÄrigheter.

JOIN
SvÄrigheter uppstÄr nÀr vi vill kombinera JOIN med varandra. Det Àr inte alltid möjligt, men det gÄr.

Ur ett parsers perspektiv har vi en nod Bli med i Expr, som har exakt tvĂ„ barn â vĂ€nster och höger. Detta Ă€r, i motsvarande ordning, vad som stĂ„r "ovanför" din JOIN och vad som stĂ„r "under" den i frĂ„gan.
Och ur ett planperspektiv handlar det om tvĂ„ barn hos en * Loop/* GĂ„ med-nod. NĂ€sta Loop, Hash Anti Join,⊠â nĂ„got sĂ„dant.
Vi anvÀnder oss av enkel logik: om vi har tabeller A och B som "joinar" med varandra i planen, kan de i frÄgan ha placerats antingen A-JOIN-B, eller B-JOIN-A. LÄt oss försöka kombinera dem sÄ, lÄt oss försöka kombinera dem omvÀnt, och sÄ lÀnge sÄdana par finns kvar.
LÄt oss ta vÄrt syntaxtrÀd, ta vÄr plan, titta pÄ dem⊠de ser inte likadana ut!

Rita om dem som grafer â Ă„h, nu börjar det likna nĂ„got!

LĂ„t oss notera att vi har noder som samtidigt har barn B och C â vi bryr oss inte om i vilken ordning. Vi kombinerar dem och vĂ€nder bilden av noden.

LĂ„t oss titta igen. Nu har vi noder med barn A och paret (B + C) â vi kombinerar dem ocksĂ„.

UtmÀrkt! Det visar sig att vi framgÄngsrikt har kombinerat dessa tvÄ JOIN frÄn frÄgan med noderna i planen.
TyvÀrr Àr denna uppgift inte alltid lösbar.

Till exempel, om frÄgan Àr A JOIN B JOIN C, men i planen kopplades först "ytterkanterna" noder A och C. Och i frÄgan finns det ingen sÄdan operator, vi har inget att markera, inget att koppla en hint till. Samma sak med "kommatecken", nÀr du skriver A, B.
Men, i de flesta fall gĂ„r det att "lösa" nĂ€stan alla noder och fĂ„ en sĂ„dan profileringsvisning till vĂ€nster över tid â bokstavligen, som i Google Chrome, nĂ€r du analyserar din JavaScript-kod. Du ser hur lĂ€nge varje rad och varje operator "har körts".

Och för att du ska kunna anvÀnda allt detta enklare, har vi gjort en lagring , dÀr du kan spara och senare hitta dina planer tillsammans med de associerade frÄgorna eller dela en lÀnk med nÄgon.
Om du bara behöver fÄ en olÀslig frÄga i ett rimligt format, anvÀnd vÄr .

KĂ€lla: habr.com
