PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan

MĂ„nga som redan anvĂ€nder explain.tensor.ru — 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


PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan

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

PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan
I denna transkription av den andra delen av min rapport vid PGConf.Russia 2020 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 «Recept för sjuka SQL-frÄgor».


Spela video

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.
PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan

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.
PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan

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.
PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan

Eftersom systemkĂ€rnan arbetar pĂ„ NodeJS, sĂ„ har vi gjort ett litet modul till det, ni kan hitta det pĂ„ GitHub. 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.
PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan

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.
PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan

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.
PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan

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 CTE Àr en absolut barriÀr för planlÀggaren.
PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan

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.
PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan
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.
PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan

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.
PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan

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.
PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan

Å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.
PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan

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.
PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan

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

PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan

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

Resultat  (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.
PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan

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.
PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan

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.
PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan

JOIN

SvÄrigheter uppstÄr nÀr vi vill kombinera JOIN med varandra. Det Àr inte alltid möjligt, men det gÄr.
PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan

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!
PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan

Rita om dem som grafer – Ă„h, nu börjar det likna nĂ„got!
PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan

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.
PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan

LĂ„t oss titta igen. Nu har vi noder med barn A och paret (B + C) – vi kombinerar dem ocksĂ„.
PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan

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.
PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan

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".
PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan

Och för att du ska kunna anvÀnda allt detta enklare, har vi gjort en lagring ett arkiv, 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 "normaliserare".

PostgreSQL Query Profiler: hur man matchar en plan med en förfrÄgan

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster