ProHoster > Blog > Bestjoer > PostgreSQL Query Profiler: hoe kinne jo plan en query oerienkomme
PostgreSQL Query Profiler: hoe kinne jo plan en query oerienkomme
In protte dy't al brûke explain.tensor.ru - ús PostgreSQL-planfisualisaasjetsjinst is miskien net bewust fan ien fan syn supermacht - in dreech te lêzen stik fan it serverlog draaie ...
... yn in prachtich ûntworpen query mei kontekstuele hints foar de oerienkommende planknooppunten:
Yn dit transkripsje fan it twadde diel fan syn rapport by PGConf.Russia 2020 Ik sil jo fertelle hoe't wy dit slagge binne.
It transkripsje fan it earste diel, wijd oan typyske query-prestaasjesproblemen en har oplossingen, is te finen yn it artikel "Rezept foar sike SQL-fragen".
Lit ús earst begjinne te kleuren - en wy sille it plan net mear kleurje, wy hawwe it al kleure, wy hawwe it al moai en begryplik, mar in fersyk.
It like ús dat mei sa'n unformattearre "blêd" it fersyk dat út it log is lutsen tige ûnsjogge sjocht en dêrom ûngemaklik.
Benammen as ûntwikkelders it lichem fan 'e oanfraach yn' e koade "lymje" (dit is fansels in antipattern, mar it bart) yn ien rigel. Ferskriklik!
Litte wy dit op ien of oare manier moaier tekenje.
En as wy dit prachtich kinne tekenje, dat is, it lichem fan 'e oanfraach demontearje en wer byinoar sette, dan kinne wy dan in hint "heakje" oan elk objekt fan dit fersyk - wat barde op it korrespondearjende punt yn it plan.
Query syntaksis beam
Om dit te dwaan, moat it fersyk earst parsed wurde.
Om't wy hawwe de kearn fan it systeem rint op NodeJS, dan hawwe wy der in module foar makke, dat kinst fyn it op GitHub. Yn feite binne dit útwreide "bindingen" oan 'e ynterne fan' e PostgreSQL-parser sels. Dat is, de grammatika is gewoan binêr gearstald en bindingen wurde makke fanút NodeJS. Wy namen de modules fan oaren as basis - d'r is gjin grut geheim hjir.
Wy fiede it lichem fan it fersyk as ynfier nei ús funksje - by de útfier krije wy in parsed syntaksisbeam yn 'e foarm fan in JSON-objekt.
No kinne wy troch dizze beam yn 'e tsjinoerstelde rjochting rinne en in fersyk gearstalle mei de ynspringen, kleurjen en opmaak dy't wy wolle. Nee, dit is net oanpasber, mar it like ús dat dit handich wêze soe.
Mapping query- en planknooppunten
Litte wy no sjen hoe't wy it plan kinne kombinearje dat wy yn 'e earste stap analysearren en de query dy't wy yn' e twadde analysearre hawwe.
Litte wy in ienfâldich foarbyld nimme - wy hawwe in query dy't in CTE genereart en dêrút twa kear lêst. Hy generearret sa'n plan.
Dit betsjut dat as wy sjogge in CTE generaasje earne yn it fersyk en in knooppunt earne yn it plan CTE, dan dizze knopen definityf "fjochtsje" mei elkoar, wy kinne se fuortendaliks kombinearje.
Probleem mei in asterisk: CTEs kinne wurde nested.
D'r binne tige min geneste, en sels mei deselde namme. Jo kinne bygelyks binnen CTE A meitsje CTE X, en op itselde nivo binnen CTE B Doch it nochris CTE X:
WITH A AS (
WITH X AS (...)
SELECT ...
)
, B AS (
WITH X AS (...)
SELECT ...
)
...
As jo fergelykje, moatte jo dit begripe. Dit "mei jo eagen" begripe - sels it plan sjen, sels it lichem fan it fersyk sjen - is heul lestich. As jo CTE-generaasje kompleks is, nested, en de oanfragen binne grut, dan is it folslein ûnbewust.
UNION
As wy in kaaiwurd hawwe yn 'e query UNION [ALL] (operator fan it ferbinen fan twa samples), dan komt it yn it plan oerien mei of in knooppunt Append, of guon Recursive Union.
Dat wat "boppe" is UNION - dit is de earste neisiet fan ús knooppunt, dat is "ûnder" - de twadde. As troch UNION wy hawwe ferskate blokken "lym" tagelyk, dan Append-d'r sil noch mar ien knooppunt wêze, mar it sil net twa hawwe, mar in protte bern - yn 'e folchoarder dy't se geane, respektivelik:
(...) -- #1
UNION ALL
(...) -- #2
UNION ALL
(...) -- #3
Append
-> ... #1
-> ... #2
-> ... #3
Probleem mei in asterisk: binnen rekursive sampling generaasje (WITH RECURSIVE) kin ek mear as ien wêze UNION. Mar allinnich it alderlêste blok nei it lêste is altyd rekursyf UNION. Alles hjirboppe is ien, mar oars UNION:
WITH RECURSIVE T AS(
(...) -- #1
UNION ALL
(...) -- #2, тут кончается генерация стартового состояния рекурсии
UNION ALL
(...) -- #3, только этот блок рекурсивный и может содержать обращение к T
)
...
Jo moatte ek sokke foarbylden "útstekke" kinne. Yn dit foarbyld sjogge wy dat UNION-d'r wiene 3 segminten yn ús fersyk. As gefolch, ien UNION соответствует Append-node, en nei de oare - Recursive Union.
Lês-skriuwe gegevens
Alles is oanlein, no witte wy hokker stik fan it fersyk oerienkomt mei hokker stik fan it plan. En yn dizze stikken kinne wy maklik en natuerlik dy objekten fine dy't "lêsber" binne.
Ut in query eachpunt, wy witte net oft it is in tabel of in CTE, mar se wurde oanwiisd troch deselde knooppunt RangeVar. En yn termen fan "lêsberens" is dit ek in frij beheinde set knopen:
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]
Wy kenne de struktuer fan it plan en de query, wy kenne de korrespondinsje fan 'e blokken, wy kenne de nammen fan' e objekten - wy meitsje in ien-op-ien ferliking.
Wer taak "mei in asterisk". Wy nimme it fersyk, fiere it út, wy hawwe gjin aliassen - wy lêze it gewoan twa kear fan deselde CTE.
Wy sjogge nei it plan - wat is it probleem? Wêrom hawwe wy in alias? Wy hawwe it net besteld. Wêr komt er sa'n "nûmernûmer" wei?
PostgreSQL foeget it sels ta. Jo moatte dat gewoan begripe krekt sa'n alias foar ús hat it foar it fergelykjen mei it plan gjin sin, it wurdt hjir gewoan tafoege. Lit ús gjin omtinken jaan oan him.
De twadde taak "mei in asterisk": as wy lêze fan in ferdielde tabel, dan krije wy in knooppunt Append of Merge Append, dy't sil bestean út in grut oantal "bern", en elk fan dat sil wêze ien of oare wize Scan'om út 'e tabel-seksje: Seq Scan, Bitmap Heap Scan of Index Scan. Mar yn alle gefallen sille dizze "bern" gjin komplekse fragen wêze - dit is hoe't dizze knooppunten kinne wurde ûnderskieden fan Append at UNION.
Wy begripe ek sokke knopen, sammelje se "yn ien stapel" en sizze: "alles wat jo lêze fan megatable is hjir en ûnder de beam".
"Ienfâldige" gegevens ûntfangende knopen
Values Scan oerienkomt yn plan VALUES yn it fersyk.
Result is in fersyk sûnder FROM soarte fan SELECT 1. Of as jo in opsetsin falske útdrukking yn hawwe WHERE-block (dan ferskynt it attribút One-Time Filter):
EXPLAIN ANALYZE
SELECT * FROM pg_class WHERE FALSE; -- или 0 = 1
Function Scan "kaart" nei de SRF's mei deselde namme.
Mar mei geneste queries is alles yngewikkelder - spitigernôch wurde se net altyd yn InitPlan/SubPlan. Soms draaie se yn ... Join of ... Anti Join, benammen as jo skriuwe wat as WHERE NOT EXISTS .... En hjir is it net altyd mooglik om se te kombinearjen - yn 'e tekst fan it plan binne d'r gjin operators dy't oerienkomme mei de knopen fan it plan.
Wer taak "mei in asterisk": guon VALUES yn it fersyk. Yn dit gefal en yn it plan krije jo ferskate knopen Values Scan.
"Nûmere" efterheaksels sille helpe om se fan elkoar te ûnderskieden - se wurde tafoege krekt yn 'e folchoarder wêryn't de oerienkommende wurde fûn VALUES-blokken lâns it fersyk fan boppe nei ûnderen.
Gegevens ferwurkjen
It liket derop dat alles yn ús fersyk is sorteare - alles wat oer is Limit.
Mar hjir is alles ienfâldich - sokke knopen as Limit, Sort, Aggregate, WindowAgg, Unique "map" ien-op-ien oan de oerienkommende operators yn it fersyk, as se binne der. D'r binne hjir gjin "stjerren" of swierrichheden.
JOIN
Swierrichheden ûntsteane as wy kombinearje wolle JOIN tusken harsels. Dit is net altyd mooglik, mar it is mooglik.
Fanút it eachpunt fan 'e query-parser hawwe wy in knooppunt JoinExpr, dy't krekt twa bern hat - lofts en rjochts. Dit is dus wat "boppe" jo JOIN is en wat "ûnder" is skreaun yn it fersyk.
En út it eachpunt fan it plan binne dat twa neikommelingen fan guon * Loop/* Join-knooppunt. Nested Loop, Hash Anti Join,... - soksawat.
Litte wy ienfâldige logika brûke: as wy tabellen A en B hawwe dy't elkoar yn it plan "meidwaan", dan kinne se yn it fersyk pleatst wurde A-JOIN-B, of B-JOIN-A. Litte wy besykje dizze manier te kombinearjen, litte wy besykje oarsom te kombinearjen, ensfh.
Litte wy ús syntaksisbeam nimme, ús plan nimme, nei har sjen ... net gelyk!
Litte wy it opnij tekenje yn 'e foarm fan grafiken - och, it liket al wat!
Lit ús note dat wy knooppunten hawwe dy't tagelyk bern B en C hawwe - it makket ús net út yn hokker folchoarder. Litte wy se kombinearje en de ôfbylding fan 'e knoop omdraaie.
Litte wy nochris sjen. No hawwe wy knooppunten mei bern A en pearen (B + C) - ek kompatibel mei har.
Grut! It docht bliken dat wy dizze twa binne JOIN út it fersyk mei it plan knopen waarden mei súkses kombinearre.
Och, dit probleem wurdt net altyd oplost.
Bygelyks, as yn in fersyk A JOIN B JOIN C, en yn it plan wiene earst de "bûtenste" knopen A en C ferbûn. Mar d'r is gjin sa'n operator yn it fersyk, wy hawwe neat om te markearjen, neat om in hint oan te heakjen. It is itselde mei de "komma" as jo skriuwe A, B.
Mar, yn 'e measte gefallen, kinne hast alle knooppunten "ûntbûn" wurde en jo kinne dit soarte profilearring op 'e lofterkant krije - letterlik, lykas yn Google Chrome as jo JavaScript-koade analysearje. Jo kinne sjen hoe lang elke rigel en elke ferklearring duorre om "út te fieren".
En om it foar jo handiger te meitsjen om dit alles te brûken, hawwe wy opslach makke argyf, wêr't jo jo plannen kinne bewarje en letter fine tegearre mei assosjearre oanfragen of de keppeling mei immen diele.
As jo gewoan in ûnlêsbere fraach yn in adekwate foarm bringe moatte, brûk dan ús "normalizer".