PostgreSQL Query Profiler: Cumu currisponde à u pianu è a dumanda
Parechji chì sò digià usu spiegà.tensor.ru - u nostru serviziu di visualizazione di u pianu PostgreSQL pò esse micca cunnisciutu di unu di i so superpoteri - turnendu un pezzu difficiuli di leghje di u logu di u servitore...
... in una dumanda bella cuncepita cù suggerimenti contextuali per i nodi di u pianu currispundenti:
In questa trascrizione di a seconda parte di u so rapportu à PGConf.Russia 2020 Vi dicu cumu avemu riisciutu à fà questu.
A trascrizione di a prima parte, dedicata à i prublemi tipici di prestazione di query è e so suluzioni, ponu esse truvati in l'articulu "Ricette per e dumande SQL malate".
Prima, cuminciamu a culurite - è ùn avemu micca più culore di u pianu, l'avemu digià culurita, l'avemu digià bella è comprensibile, ma una dumanda.
Ci pareva chì cun un "fogliu" cusì senza formate a dumanda tirata da u logu pare assai brutta è per quessa inconveniente.
In particulare quandu i sviluppatori "colanu" u corpu di a dumanda in u codice (questu hè, sicuru, un antipattern, ma succede) in una linea. Horrible!
Disegnemu questu in una manera più bella.
È se pudemu disegnà stu bellu, vale à dì, disassemble è riunite u corpu di a dumanda, allora pudemu "attacà" un suggerimentu à ogni ughjettu di sta dumanda - ciò chì hè accadutu à u puntu currispundente in u pianu.
Query syntax tree
Per fà questu, a dumanda deve esse prima analizata.
Perchè avemu u core di u sistema corre nantu à NodeJS, allora avemu fattu un modulu per questu, pudete truvà lu in GitHub. In fatti, questi sò estesi "bindings" à l'internu di u parser PostgreSQL stessu. Vale à dì, a grammatica hè simplicemente compilata binaria è ligami sò fatti da NodeJS. Avemu pigliatu i moduli di l'altri cum'è una basa - ùn ci hè micca un grande sicretu quì.
Avemu alimentatu u corpu di a dumanda cum'è input à a nostra funzione - à l'output avemu un arbulu di sintassi analizatu in forma di un oggettu JSON.
Avà pudemu passà per questu arbre in a direzzione opposta è assemble una dumanda cù l'indentazioni, u culore è u furmatu chì vulemu. No, questu ùn hè micca persunalizabile, ma ci paria chì questu seria cunvene.
Mapping query and plan nodes
Avà vedemu cumu pudemu cumminà u pianu chì avemu analizatu in u primu passu è a quistione chì avemu analizatu in u sicondu.
Pigliemu un esempiu simplice - avemu una dumanda chì genera un CTE è leghje da ellu duie volte. Ellu genera un tali pianu.
Questu significa chì si vede una generazione CTE in un locu in a dumanda è un node in un locu in u pianu CTE, Allora sti nodi definitamente "lottanu" cù l'altri, pudemu immediatamente cumminà.
Prublemu cù un asteriscu: I CTE ponu esse nidificati.
Ci sò assai pocu nidificatu, è ancu quelli di u stessu nome. Per esempiu, pudete dentru CTE A fà CTE X, è à u listessu livellu internu CTE B fà di novu CTE X:
WITH A AS (
WITH X AS (...)
SELECT ...
)
, B AS (
WITH X AS (...)
SELECT ...
)
...
Quandu si compara, duvete capisce questu. Capisce questu "cù i vostri ochji" - ancu vede u pianu, ancu vede u corpu di a dumanda - hè assai difficiule. Se a vostra generazione CTE hè cumplessa, nidificata, è e dumande sò grandi, allora hè completamente inconsciente.
UNION
Se avemu una keyword in a dumanda UNION [ALL] (operatore di unisce dui campioni), poi in u pianu currisponde sia à un nodu Append, o certi Recursive Union.
Ciò chì hè "sopra" sopra UNION - questu hè u primu discendente di u nostru node, chì hè "sottu" - u sicondu. Se attraversu UNION avemu parechji blocchi "incollati" à una volta, allora Append- Ci sarà sempre un solu node, ma ùn averà micca dui, ma parechji zitelli - in l'ordine chì vanu, rispettivamente:
(...) -- #1
UNION ALL
(...) -- #2
UNION ALL
(...) -- #3
Append
-> ... #1
-> ... #2
-> ... #3
Prublemu cù un asteriscu: generazione di campionamentu recursivu (WITH RECURSIVE) pò ancu esse più di unu UNION. Ma solu l'ultimu bloccu dopu à l'ultimu hè sempre recursive UNION. Tuttu sopra hè unu, ma sfarente UNION:
WITH RECURSIVE T AS(
(...) -- #1
UNION ALL
(...) -- #2, тут кончается генерация стартового состояния рекурсии
UNION ALL
(...) -- #3, только этот блок рекурсивный и может содержать обращение к T
)
...
Avete ancu bisognu di pudè "stà fora" tali esempi. In questu esempiu avemu vistu chì UNION- Ci era 3 segmenti in a nostra dumanda. Per quessa, unu UNION соответствует Append-node, è à l'altru - Recursive Union.
Dati di lettura-scrittura
Tuttu hè dispostu, avà sapemu quale pezzu di a dumanda currisponde à quale pezzu di u pianu. È in questi pezzi pudemu truvà facilmente è naturalmente quelli ogetti chì sò "leghjite".
Da un puntu di vista di quistione, ùn sapemu micca s'ellu hè una tavola o un CTE, ma sò designati da u stessu node. RangeVar. È in termini di "leggibilità", questu hè ancu un settore abbastanza limitatu di nodi:
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]
Sapemu a struttura di u pianu è a dumanda, sapemu a currispundenza di i blocchi, sapemu i nomi di l'uggetti - facemu un paragone unu à unu.
Di novu tâche "avec un astérisque". Pigliemu a dumanda, eseguite, ùn avemu micca alias - avemu solu leghje duie volte da u stessu CTE.
Fighjemu u pianu - chì hè u prublema? Perchè avemu avutu un alias? Ùn avemu micca urdinatu. Induve uttene un tali "numeru numeru"?
PostgreSQL l'aghjunghje stessu. Basta à capisce chì ghjustu un tali alias per noi, per u scopu di paragunà cù u pianu, ùn hà micca sensu, hè solu aghjuntu quì. Ùn fate micca attente à ellu.
U sicondu tâche "avec un astérisque": se leghjemu da una tavola partizionata, allora averemu un node Append o Merge Append, chì sarà custituitu da un gran numaru di "figlioli", è ognunu di quale serà in qualchì manera Scan'om da a sezione di tabella: Seq Scan, Bitmap Heap Scan o Index Scan. Ma, in ogni casu, questi "figlioli" ùn saranu micca dumande cumplessu - questu hè cumu si ponu distingue questi nodi da Append at UNION.
Capemu ancu tali nodi, cullà "in una pila" è dicenu: "tuttu ciò chì leghje da megatable hè quì è in l'arburu".
Nodi "semplici" di riceve dati
Values Scan currisponde à u pianu VALUES in a dumanda.
Result hè una dumanda senza FROM un tipu di SELECT 1. O quandu avete una espressione deliberatamente falsa in WHERE-block (poi l'attributu appare One-Time Filter):
EXPLAIN ANALYZE
SELECT * FROM pg_class WHERE FALSE; -- или 0 = 1
Ma cù e dumande nidificate tuttu hè più cumplicatu - sfurtunatamenti, ùn sò micca sempre diventati InitPlan/SubPlan. Calchì volta si trasformanu in ... Join o ... Anti Join, soprattuttu quandu scrivi qualcosa cum'è WHERE NOT EXISTS .... È quì ùn hè micca sempre pussibule cunghjuntà - in u testu di u pianu ùn ci sò micca operatori chì currispondenu à i nodi di u pianu.
Di novu tâche "avec un astérisque": un pò VALUES in a dumanda. In questu casu è in u pianu uttene parechji nodi Values Scan.
I suffissi "numerati" aiutanu à distinguelli l'una di l'altru - sò aghjuntu esattamente in l'ordine in quale si trovanu i currispondenti. VALUES-blocks longu a dumanda da cima à fondu.
Trattamentu di dati
Sembra chì tuttu in a nostra dumanda hè stata risolta - tuttu ciò chì resta hè Limit.
Ma quì tuttu hè simplice - tali nodi cum'è Limit, Sort, Aggregate, WindowAgg, Unique "mappa" unu à unu à l'operatori currispundenti in a dumanda, se ci sò. Ùn ci hè micca "stella" o difficultà quì.
JOIN
I difficultà si sviluppanu quandu vulemu cumminà JOIN trà elli. Questu ùn hè micca sempre pussibule, ma hè pussibule.
Da u puntu di vista di u parser di query, avemu un node JoinExpr, chì hà esattamente dui figlioli - left and right. Questu, dunque, hè ciò chì hè "sopra" u vostru JOIN è ciò chì hè scrittu "sottu" in a dumanda.
È da u puntu di vista di u pianu, questi sò dui discendenti di certi * Loop/* Join-node. Nested Loop, Hash Anti Join,... - qualcosa cusì.
Utilizemu una logica simplice: s'ellu avemu i tavule A è B chì "uniscenu" in u pianu, allora in a dumanda puderanu esse situati sia A-JOIN-B, o B-JOIN-A. Pruvemu di cumminà stu modu, pruvemu à cumminà l'altru, è cusì finu à chì avemu da esse fora di tali coppie.
Pigliamu u nostru arbulu di sintassi, pigliate u nostru pianu, fighjateli... micca simili !
Ridighjemu in forma di grafici - oh, pare digià qualcosa!
Avemu nutà chì avemu nodes chì simultaneamente anu figlioli B è C - ùn importa micca in quale ordine. Cumbinemu è vultà a stampa di u node.
Fighjemu di novu. Avà avemu node cù i zitelli A è parigli (B + C) - cumpatibili ancu cun elli.
Perfettu! Risulta chì simu sti dui JOIN da a dumanda cù i nodi di u pianu sò stati cumminati cun successu.
Alas, stu prublema ùn hè micca sempre risolta.
Per esempiu, se in una dumanda A JOIN B JOIN C, è in u pianu, prima di tuttu, i nodi "esterni" A è C sò stati cunnessi. Ma ùn ci hè micca un tali operatore in a dumanda, ùn avemu nunda di mette in risaltu, nunda di aghjunghje un suggerimentu. Hè listessa cù a "virgula" quandu scrivi A, B.
Ma, in a maiò parte di i casi, quasi tutti i nodi ponu esse "slegati" è pudete uttene stu tipu di prufilu à a manca in u tempu - literalmente, cum'è in Google Chrome quandu analizà u codice JavaScript. Pudete vede quantu tempu ogni linea è ogni dichjarazione hà pigliatu per "eseguite".
È per fà più còmuda per voi di utilizà tuttu questu, avemu fattu u almacenamentu archiviu, induve pudete salvà è dopu truvà i vostri piani cù e dumande assuciate o sparte u ligame cù qualchissia.
Se avete solu bisognu di purtà una dumanda illegibile in una forma adatta, aduprate u nostru "normalizzatore".