PostgreSQL Query Profiler: hur man matchar plan och fråga

Många som redan använder explain.tensor.ru - vår PostgreSQL-planvisualiseringstjänst kanske inte är medveten om en av dess superkrafter - vänder en svårläst del av serverloggen...

PostgreSQL Query Profiler: hur man matchar plan och fråga
... till en vackert designad fråga med kontextuella tips för motsvarande plannoder:

PostgreSQL Query Profiler: hur man matchar plan och fråga
I denna avskrift av andra delen av hans rapport på PGConf.Russia 2020 Jag ska berätta hur vi lyckades göra det här.

Transkriptionen av den första delen, tillägnad typiska frågeprestandaproblem och deras lösningar, finns i artikeln "Recept för krångliga SQL-frågor".



Låt oss först börja färglägga - och vi kommer inte längre att färglägga planen, vi har redan färglagt den, vi har redan den vacker och begriplig, men en begäran.

Det verkade för oss att med ett sådant oformaterat "ark" ser begäran från loggen väldigt ful ut och därför obekväm.
PostgreSQL Query Profiler: hur man matchar plan och fråga

Särskilt när utvecklare "limmar" förfrågans kropp i koden (detta är naturligtvis ett antimönster, men det händer) på en rad. Fruktansvärd!

Låt oss rita det här på något sätt vackrare.
PostgreSQL Query Profiler: hur man matchar plan och fråga

Och om vi kan rita detta vackert, det vill säga demontera och sätta ihop förfrågans kropp, kan vi sedan "bifoga" en ledtråd till varje föremål för denna begäran - vad som hände vid motsvarande punkt i planen.

Frågesyntaxträd

För att göra detta måste begäran först analyseras.
PostgreSQL Query Profiler: hur man matchar plan och fråga

För det har vi kärnan i systemet körs på NodeJS, sedan gjorde vi en modul för det, du kan hitta den på GitHub. I själva verket är dessa utökade "bindningar" till det interna i själva PostgreSQL-parsern. Det vill säga, grammatiken är helt enkelt binär kompilerad och bindningar görs till den från NodeJS. Vi tog andras moduler som grund – här finns ingen stor hemlighet.

Vi matar förfrågans brödtext som input till vår funktion - vid utgången får vi ett parsat syntaxträd i form av ett JSON-objekt.
PostgreSQL Query Profiler: hur man matchar plan och fråga

Nu kan vi gå igenom det här trädet i motsatt riktning och sammanställa en begäran med de indrag, färg och formatering som vi vill ha. Nej, detta är inte anpassningsbart, men det verkade för oss att det skulle vara bekvämt.
PostgreSQL Query Profiler: hur man matchar plan och fråga

Mappning av fråge- och plannoder

Låt oss nu se hur vi kan kombinera planen som vi analyserade i det första steget och frågan som vi analyserade i det andra.

Låt oss ta ett enkelt exempel - vi har en fråga som genererar en CTE och läser från den två gånger. Han skapar en sådan plan.
PostgreSQL Query Profiler: hur man matchar plan och fråga

CTE

Om du tittar noga på det, upp till version 12 (eller börjar från det med nyckelordet MATERIALIZED) bildning CTE är en absolut barriär för planeraren.
PostgreSQL Query Profiler: hur man matchar plan och fråga

Det betyder att om vi ser en CTE-generation någonstans i förfrågan och en nod någonstans i planen CTE, då dessa noder definitivt "kämpar" med varandra, vi kan omedelbart kombinera dem.

Problem med en asterisk: CTE kan kapslas.
PostgreSQL Query Profiler: hur man matchar plan och fråga
Det finns mycket dåligt kapslade sådana, och till och med de med samma namn. Till exempel kan du inuti CTE A göra CTE X, och på samma nivå inuti CTE B gör det igen CTE X:

WITH A AS (
  WITH X AS (...)
  SELECT ...
)
, B AS (
  WITH X AS (...)
  SELECT ...
)
...

När du jämför måste du förstå detta. Att förstå detta "med dina ögon" - även att se planen, till och med se innehållet i begäran - är mycket svårt. Om din CTE-generering är komplex, kapslad och förfrågningarna är stora, är den helt omedveten.

UNION

Om vi ​​har ett nyckelord i frågan UNION [ALL] (operatör för att sammanfoga två sampel), då motsvarar det i planen antingen en nod Append, eller några Recursive Union.
PostgreSQL Query Profiler: hur man matchar plan och fråga

Det som är "ovanför" ovan UNION - det här är den första ättlingen till vår nod, som är "under" - den andra. Om genom UNION vi har flera block "limmade" på en gång, alltså Append-det kommer fortfarande bara att finnas en nod, men den kommer att ha inte två, utan många barn - i den ordning de går, respektive:

  (...) -- #1
UNION ALL
  (...) -- #2
UNION ALL
  (...) -- #3

Append
  -> ... #1
  -> ... #2
  -> ... #3

Problem med en asterisk: inom rekursiv samplingsgenerering (WITH RECURSIVE) kan också vara mer än en UNION. Men bara det allra sista blocket efter det sista är alltid rekursivt UNION. Allt ovan är ett, men olika UNION:

WITH RECURSIVE T AS(
  (...) -- #1
UNION ALL
  (...) -- #2, тут кончается генерация стартового состояния рекурсии
UNION ALL
  (...) -- #3, только этот блок рекурсивный и может содержать обращение к T
)
...

Du måste också kunna "sticka ut" sådana exempel. I det här exemplet ser vi det UNION-Det fanns 3 segment i vår förfrågan. Följaktligen en UNION motsvarar Append-nod, och till den andra - Recursive Union.
PostgreSQL Query Profiler: hur man matchar plan och fråga

Läs-skriv data

Allt är upplagt, nu vet vi vilken del av förfrågan som motsvarar vilken del av planen. Och i dessa bitar kan vi enkelt och naturligt hitta de föremål som är "läsbara".

Ur frågesynpunkt vet vi inte om det är en tabell eller en CTE, men de är designade av samma nod RangeVar. Och när det gäller "läsbarhet" är detta också en ganska begränsad uppsättning noder:

  • 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]

Vi känner till strukturen för planen och frågan, vi känner till blockens överensstämmelse, vi känner till namnen på objekten - vi gör en en-till-en-jämförelse.
PostgreSQL Query Profiler: hur man matchar plan och fråga

Igen uppgift "med en asterisk". Vi tar förfrågan, utför den, vi har inga alias - vi läser den bara två gånger från samma CTE.
PostgreSQL Query Profiler: hur man matchar plan och fråga

Vi tittar på planen – vad är problemet? Varför hade vi ett alias? Vi beställde det inte. Var får han ett sådant "nummernummer"?

PostgreSQL lägger till det själv. Du behöver bara förstå det bara ett sådant alias för oss, för att jämföra med planen, är det inte meningsfullt, det läggs helt enkelt till här. Låt oss inte uppmärksamma honom.

andra uppgift "med en asterisk": om vi läser från en partitionerad tabell kommer vi att få en nod Append eller Merge Append, som kommer att bestå av ett stort antal "barn", och var och en kommer att vara på något sätt Scan'om från tabellsektionen: Seq Scan, Bitmap Heap Scan eller Index Scan. Men i alla fall kommer dessa "barn" inte att vara komplexa frågor - det är så dessa noder kan särskiljas från Append при UNION.
PostgreSQL Query Profiler: hur man matchar plan och fråga

Vi förstår också sådana knutar, samlar dem "i en hög" och säger: "allt du läser från megatable är här och ner i trädet".

"Enkla" datamottagande noder

PostgreSQL Query Profiler: hur man matchar plan och fråga

Values Scan överensstämmer i plan VALUES i begäran.

Result är en begäran utan FROM tycka om SELECT 1. Eller när du har ett medvetet falskt uttryck i dig WHERE-block (då visas attributet One-Time Filter):

EXPLAIN ANALYZE
SELECT * FROM pg_class WHERE FALSE; -- или 0 = 1

Result  (cost=0.00..0.00 rows=0 width=230) (actual time=0.000..0.000 rows=0 loops=1)
  One-Time Filter: false

Function Scan "map" till SRF med samma namn.

Men med kapslade frågor är allt mer komplicerat - tyvärr blir de inte alltid till InitPlan/SubPlan. Ibland förvandlas de till ... Join eller ... Anti Join, speciellt när du skriver något som WHERE NOT EXISTS .... Och här är det inte alltid möjligt att kombinera dem - i planens text finns det inga operatörer som motsvarar planens noder.

Igen uppgift "med en asterisk": några VALUES i begäran. I det här fallet och i planen får du flera noder Values Scan.
PostgreSQL Query Profiler: hur man matchar plan och fråga

"Numrerade" suffix hjälper till att skilja dem från varandra - de läggs till exakt i den ordning som motsvarande hittas VALUES-block längs begäran från topp till botten.

Databehandling

Det verkar som att allt i vår förfrågan har sorterats ut - allt som återstår är Limit.
PostgreSQL Query Profiler: hur man matchar plan och fråga

Men här är allt enkelt - sådana noder som Limit, Sort, Aggregate, WindowAgg, Unique "map" en-till-en till motsvarande operatörer i begäran, om de finns där. Det finns inga "stjärnor" eller svårigheter här.
PostgreSQL Query Profiler: hur man matchar plan och fråga

JOIN

Svårigheter uppstår när vi vill kombinera JOIN sinsemellan. Detta är inte alltid möjligt, men det är möjligt.
PostgreSQL Query Profiler: hur man matchar plan och fråga

Ur frågeparserns synvinkel har vi en nod JoinExpr, som har exakt två barn - vänster och höger. Detta är följaktligen vad som är "ovanför" din JOIN och vad som står "under" den i begäran.

Och ur planens synvinkel är det här två ättlingar till några * Loop/* Join-nod. Nested Loop, Hash Anti Join,... - något sådant.

Låt oss använda enkel logik: om vi har tabellerna A och B som "förenar sig" med varandra i planen, kan de i förfrågan finnas antingen A-JOIN-BEller B-JOIN-A. Låt oss försöka kombinera på det här sättet, låt oss försöka kombinera tvärtom, och så vidare tills vi får slut på sådana par.

Låt oss ta vårt syntaxträd, ta vår plan, titta på dem... inte lika!
PostgreSQL Query Profiler: hur man matchar plan och fråga

Låt oss rita om det i form av grafer - åh, det ser redan ut som något!
PostgreSQL Query Profiler: hur man matchar plan och fråga

Låt oss notera att vi har noder som samtidigt har barn B och C - vi bryr oss inte om i vilken ordning. Låt oss kombinera dem och vända bilden av noden.
PostgreSQL Query Profiler: hur man matchar plan och fråga

Låt oss titta igen. Nu har vi noder med barn A och par (B + C) - kompatibla med dem också.
PostgreSQL Query Profiler: hur man matchar plan och fråga

Bra! Det visar sig att vi är dessa två JOIN från begäran med plannoderna kombinerades framgångsrikt.

Tyvärr är detta problem inte alltid löst.
PostgreSQL Query Profiler: hur man matchar plan och fråga

Till exempel om i en förfrågan A JOIN B JOIN C, och i planen var först och främst de "yttre" noderna A och C anslutna. Men det finns ingen sådan operatör i förfrågan, vi har inget att markera, inget att bifoga en ledtråd till. Det är samma sak med "komma" när du skriver A, B.

Men i de flesta fall kan nästan alla noder "lösgöras" och du kan få den här typen av profilering till vänster i tid - bokstavligen, som i Google Chrome när du analyserar JavaScript-kod. Du kan se hur lång tid varje rad och varje påstående tog att "exekveras".
PostgreSQL Query Profiler: hur man matchar plan och fråga

Och för att göra det bekvämare för dig att använda allt detta har vi gjort förvaring arkivet, där du kan spara och senare hitta dina planer tillsammans med tillhörande förfrågningar eller dela länken med någon.

Om du bara behöver föra en oläslig fråga i en adekvat form, använd vår "normaliserare".

PostgreSQL Query Profiler: hur man matchar plan och fråga

Källa: will.com

Lägg en kommentar