PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel

Mange, der allerede bruger explain.tensor.ru - vores PostgreSQL-planvisualiseringstjeneste er muligvis ikke opmærksom på en af ​​dens superkræfter - at vende en svær at læse del af serverloggen ...

PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel
… til en smukt designet forespørgsel med kontekstuelle tip til de relevante planknuder:

PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel
I denne afskrift af anden del af hans rapport på PGConf.Russia 2020 Jeg vil fortælle dig, hvordan vi formåede at gøre det.

Transskriptionen af ​​den første del, der er afsat til typiske forespørgselsydeevneproblemer og deres løsninger, kan findes i artiklen "Opskrifter på syge SQL-forespørgsler".



Lad os først farvelægge - og vi vil ikke længere farvelægge planen, vi har allerede malet den, den er allerede smuk og forståelig, men anmodningen.

Det forekom os, at anmodningen trukket ud af loggen med et uformateret "ark" ser meget grimt ud og derfor ubelejligt.
PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel

Især når udviklere "limer" anmodningslegemet i koden (dette er selvfølgelig et anti-mønster, men det sker) på én linje. Rædsel!

Lad os tegne det på en eller anden måde smukkere.
PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel

Og hvis vi kan tegne det smukt, det vil sige adskille og samle anmodningslegemet tilbage, så kan vi "vedhæfte" et hint til hvert objekt af denne anmodning - hvad der skete på det tilsvarende punkt i planen.

Forespørgselssyntakstræ

For at gøre dette skal forespørgslen først parses.
PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel

For det har vi kernen i systemet kører på NodeJS, så har vi lavet et modul til det, kan du find det på github. Faktisk er disse udvidede "bindinger" til det indre af selve PostgreSQL-parseren. Det vil sige, at grammatikken simpelthen er binært kompileret, og bindinger er lavet til den af ​​NodeJS. Vi tog andres moduler som udgangspunkt – her er ingen stor hemmelighed.

Vi fodrer input-anmodningens krop til vores funktion - ved udgangen får vi et parset syntakstræ i form af et JSON-objekt.
PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel

Nu kan vi gå gennem dette træ i den modsatte retning og samle anmodningen med de indrykker, farvelægning, formatering, vi ønsker. Nej, dette kan ikke konfigureres, men vi troede, at det ville være praktisk.
PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel

Kortlægning forespørgsel og plan noder

Lad os nu se, hvordan vi kan kombinere planen, som vi analyserede i det første trin, og forespørgslen, som vi analyserede i det andet.

Lad os tage et simpelt eksempel - vi har en anmodning, der danner en CTE og læser den to gange. Han genererer sådan en plan.
PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel

CTE

Hvis du ser grundigt på det, før den 12. version (eller starter fra den med søgeordet MATERIALIZED) dannelse CTE er en ubetinget barriere for planlæggeren.
PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel

Og det betyder, at hvis vi et sted i anmodningen ser genereringen af ​​CTE og et eller andet sted i planen noden CTE, så "kæmper" disse noder unikt indbyrdes, vi kan straks kombinere dem.

Opgave "med en stjerne"Bemærk: CTE'er kan indlejres.
PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel
Der er meget dårligt indlejrede, og endda det samme navn. For eksempel kan du indenfor CTE A lave CTE X, og på samme niveau indeni CTE B gør igen CTE X:

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

Når du sammenligner, skal du forstå dette. Det er meget svært at forstå dette med "øjne" - selv at se planen, selv at se selve anmodningen. Hvis din CTE-generation er kompleks, indlejret, anmodningerne er store, så er den fuldstændig ubevidst.

UNION

Hvis vi har et nøgleord i anmodningen UNION [ALL] (operatøren for at forbinde to prøver), så svarer det i planen til enten knudepunktet Append, eller nogle Recursive Union.
PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel

Det der er "over" UNION - dette er det første barn af vores knude, som er "nedefra" - det andet. Hvis igennem UNION vi har altså "limet" flere klodser på én gang Append-node vil stadig kun have én, men den vil ikke have to børn, men mange - i rækkefølge efterhånden:

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

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

Opgave "med en stjerne": inde i generering af rekursiv hentning (WITH RECURSIVE) kan også være mere end én UNION. Men kun den allersidste blok efter den sidste er altid rekursiv UNION. Alt ovenfor er ét, men forskelligt UNION:

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

Sådanne eksempler skal også kunne "indsætte". I dette eksempel ser vi det UNION-segmenter i vores anmodning var 3 stk. Følgelig en UNION svarer til Append-node, og den anden - Recursive Union.
PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel

Læse-skrive data

Alt, lagt ud, nu ved vi, hvilken del af anmodningen, der svarer til hvilken del af planen. Og i disse stykker kan vi nemt og naturligt finde de objekter, der er "læsbare".

Fra forespørgslens synspunkt ved vi ikke, om dette er en tabel eller en CTE, men de er angivet med den samme node RangeVar. Og i den "læsbare" plan er dette også et ret begrænset sæt 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 kender strukturen af ​​planen og anmodningen, vi kender blokkenes korrespondance, vi kender navnene på objekterne - vi foretager en entydig sammenligning.
PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel

En gang til opgave "med en stjerne". Vi tager en anmodning, udfører den, vi har ingen aliaser - vi læser den bare to gange fra en CTE.
PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel

Lad os se på planen - hvad er problemet? Hvorfor havde vi et alias? Vi bestilte det ikke. Hvor får han sådan et "nummer" fra?

PostgreSQL tilføjer det selv. Det skal du bare forstå bare sådan et alias for os giver det for sammenligning med planen ingen mening, det tilføjes blot her. Lad os ikke være opmærksomme på ham.

Den anden opgave "med en stjerne": hvis vi læser fra en opdelt tabel, får vi en node Append eller Merge Append, som vil bestå af et stort antal "børn", og som hver især vil være nogle Scan'om fra tabel-sektionen: Seq Scan, Bitmap Heap Scan eller Index Scan. Men under alle omstændigheder vil disse "børn" ikke være komplekse forespørgsler - det er sådan, disse noder kan skelnes fra Append при UNION.
PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel

Vi forstår også sådanne knob, vi samler dem "i en bunke" og siger: "alt hvad du læser fra megatable er lige her og nede i træet".

"Simple" dataindsamlingsknuder

PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel

Values Scan i plan svarer VALUES i anmodningen.

Result er en anmodning uden FROM slags SELECT 1. Eller når du har et falsk udtryk i WHERE-blok (så vises attributten 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 "Mapyatsya" på SRF af samme navn.

Men med indlejrede forespørgsler er alt mere kompliceret - desværre bliver de ikke altid til InitPlan/SubPlan. Nogle gange bliver de til ... Join eller ... Anti Join, især når du skriver sådan noget WHERE NOT EXISTS .... Og det er ikke altid muligt at kombinere der - i planens tekst er der ingen operatører, der svarer til planens noder.

En gang til opgave "med en stjerne": nogle VALUES i anmodningen. I dette tilfælde og i planen får du flere noder Values Scan.
PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel

"Nummer"-suffikser hjælper med at skelne dem fra hinanden - det tilføjes nøjagtigt i den rækkefølge, som de tilsvarende VALUES-blokerer i løbet af anmodningen fra top til bund.

Databehandling

Det ser ud til, at alt i vores anmodning blev sorteret - kun Limit.
PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel

Men alt er enkelt her - sådanne noder som Limit, Sort, Aggregate, WindowAgg, Unique De "map" en-til-en til de tilsvarende operatører i anmodningen, hvis de er der. Der er ingen "stjerner" og ingen vanskeligheder.
PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel

JOIN

Der opstår vanskeligheder, når vi vil kombinere JOIN indbyrdes. Dette er ikke altid muligt, men det er muligt.
PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel

Fra forespørgselsparserens synspunkt har vi en node JoinExpr, som har præcis to børn - venstre og højre. Dette er henholdsvis det, der er "over" din JOIN, og hvad der står "under" det i forespørgslen.

Og set fra planens synspunkt er der tale om to efterkommere af nogle * Loop/* Join-knude. Nested Loop, Hash Anti Join... er sådan noget.

Lad os bruge simpel logik: hvis vi har tabel A og B, der "forener" hinanden i planen, så kan de i forespørgslen være placeret enten A-JOIN-BEller B-JOIN-A. Lad os prøve at kombinere sådan her, prøve at kombinere på den modsatte måde, og så videre, indtil sådanne par løber tør.

Tag vores syntakstræ, tag vores plan, se på dem... det ligner det ikke!
PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel

Lad os tegne det om i form af grafer - åh, noget er allerede blevet lig noget!
PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel

Lad os lægge mærke til, at vi har noder, der har børn B og C på samme tid – det er lige meget for os i hvilken rækkefølge. Lad os kombinere dem og vende billedet af noden.
PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel

Lad os se igen. Nu har vi noder med børn A og par (B + C) - kompatible med dem.
PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel

Store! Det viser sig, at vi er disse to JOIN fra forespørgslen med planens noder blev kombineret med succes.

Desværre er dette problem ikke altid løst.
PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel

For eksempel hvis anmodningen A JOIN B JOIN C, og i planen blev de "ekstreme" noder A og C forbundet først og fremmest. Men der er ingen sådan operatør i forespørgslen, vi har intet at fremhæve, der er intet at binde hintet til. Det samme med "komma", når du skriver A, B.

Men i de fleste tilfælde formår næsten alle noder at "løsne" og få en sådan profilering til venstre i tide - bogstaveligt talt, som i Google Chrome, når du analyserer JavaScript-kode. Du kan se, hvor længe hver linje og hver sætning "eksekveres".
PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel

Og for at gøre det nemmere for dig at bruge alt dette, har vi lavet opbevaring arkiv, hvor du kan gemme og derefter finde dine planer sammen med tilknyttede forespørgsler eller dele et link med nogen.

Hvis du bare skal bringe en ulæselig anmodning i en passende form, så brug vores "normalizer".

PostgreSQL Query Profiler: Sådan matcher du plan og forespørgsel

Kilde: www.habr.com

Tilføj en kommentar