PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu

Daudzi, kas jau izmanto paskaidrojiet.tensor.ru - mÅ«su PostgreSQL plāna vizualizācijas pakalpojums, iespējams, nezina vienu no tā lielvarām ā€” grÅ«ti salasāmas servera žurnāla daļas pagrieÅ”ana...

PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu
... skaisti izstrādātā vaicājumā ar kontekstuāliem padomiem attiecīgajiem plāna mezgliem:

PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu
Å ajā stenogrammā viņa otrā daļa ziņojums PGConf.Russia 2020 Es jums pastāstÄ«Å”u, kā mums tas izdevās.

Pirmās daļas atÅ”ifrējums, kas veltÄ«ts tipiskām vaicājumu veiktspējas problēmām un to risinājumiem, ir atrodams rakstā "Receptes slimiem SQL vaicājumiem".



Vispirms sāksim krāsot - un plānu vairs nekrāsosim, jau izkrāsojām, mums jau ir skaisti un saprotami, bet lūgums.

Mums Ŕķita, ka ar tik neformatētu ā€œloksniā€ no baļķa izvilktais pieprasÄ«jums izskatās ļoti neglÄ«ts un tāpēc neērts.
PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu

It Ä«paÅ”i, ja izstrādātāji ā€œielÄ«mēā€ pieprasÄ«juma pamattekstu kodā (tas, protams, ir antiraksts, bet tas notiek) vienā rindā. Å ausmÄ«gi!

UzzÄ«mēsim Å”o kaut kā skaistāk.
PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu

Un, ja mēs varam to skaisti uzzÄ«mēt, tas ir, izjaukt un atkal salikt kopā pieprasÄ«juma pamattekstu, tad mēs varam ā€œpievienotā€ mājienu katram Ŕī pieprasÄ«juma objektam - kas notika attiecÄ«gajā plāna punktā.

Vaicājuma sintakses koks

Lai to izdarītu, vispirms ir jāparsē pieprasījums.
PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu

Jo mums ir sistēmas kodols darbojas uz NodeJS, tad mēs tam izveidojām moduli, jÅ«s varat atrodiet to vietnē GitHub. Faktiski tās ir paplaÅ”inātas ā€œsaistÄ«basā€ ar paÅ”a PostgreSQL parsētāja iekŔējiem elementiem. Tas nozÄ«mē, ka gramatika ir vienkārÅ”i bināra kompilācija, un tai tiek izveidoti saistÄ«jumi no NodeJS. Mēs ņēmām par pamatu citu cilvēku moduļus - Å”eit nav nekāda lielā noslēpuma.

Mēs ievadām pieprasījuma pamattekstu kā ievadi mūsu funkcijai - izejā mēs iegūstam parsētu sintakses koku JSON objekta formā.
PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu

Tagad mēs varam skriet cauri Å”im kokam pretējā virzienā un apkopot pieprasÄ«jumu ar vajadzÄ«gajām atkāpēm, krāsojumu un formatējumu. Nē, tas nav pielāgojams, bet mums Ŕķita, ka tas bÅ«tu ērti.
PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu

KartÄ“Å”anas vaicājuma un plāna mezgli

Tagad redzēsim, kā mēs varam apvienot plānu, kuru analizējām pirmajā darbībā, un vaicājumu, ko analizējām otrajā darbībā.

Ņemsim vienkārÅ”u piemēru ā€“ mums ir vaicājums, kas Ä£enerē CTE un nolasa no tā divas reizes. ViņŔ izstrādā Ŕādu plānu.
PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu

CTE

Ja paskatās uzmanÄ«gi, lÄ«dz versijai 12 (vai sākot no tās ar atslēgvārdu MATERIALIZED) veidoÅ”anās CTE ir absolÅ«ts Ŕķērslis plānotājam.
PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu

Tas nozÄ«mē, ka, ja mēs redzam CTE paaudzi kaut kur pieprasÄ«jumā un mezglu kaut kur plānā CTE, tad Å”ie mezgli noteikti ā€œcÄ«nāsā€ savā starpā, varam uzreiz tos apvienot.

Problēma ar zvaigznīti: CTE var ligzdot.
PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu
Ir ļoti slikti ligzdoti, un pat tādi, kuriem ir tāds pats nosaukums. Piemēram, jÅ«s varat iekŔā CTE A padarÄ«t CTE X, un tajā paŔā lÄ«menÄ« iekÅ”pusē CTE B izdari to vēlreiz CTE X:

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

SalÄ«dzinot, jums tas ir jāsaprot. Saprast to ā€œar acÄ«mā€ - pat redzēt plānu, pat redzēt pieprasÄ«juma pamattekstu - ir ļoti grÅ«ti. Ja jÅ«su CTE paaudze ir sarežģīta, ligzdota un pieprasÄ«jumi ir lieli, tas ir pilnÄ«gi neapzināts.

SAVIENÄŖBA

Ja vaicājumā ir atslēgvārds UNION [ALL] (divu paraugu savienoÅ”anas operators), tad plānā tas atbilst vai nu mezglam Append, vai daži Recursive Union.
PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu

Tas, kas ir "augŔā" augŔā UNION - Å”is ir mÅ«su mezgla pirmais pēcnācējs, kas atrodas ā€œapakŔāā€ - otrais. Ja cauri UNION tad mums ir uzreiz ā€œpielÄ«mētiā€ vairāki bloki Append-joprojām bÅ«s tikai viens mezgls, bet tam bÅ«s nevis divi, bet daudzi bērni - attiecÄ«gi secÄ«bā, kādā tie iet:

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

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

Problēma ar zvaigznÄ«ti: iekŔā rekursÄ«vās izlases Ä£enerÄ“Å”ana (WITH RECURSIVE) var bÅ«t arÄ« vairāk nekā viens UNION. Bet tikai pats pēdējais bloks pēc pēdējā vienmēr ir rekursÄ«vs UNION. Viss augstāk ir viens, bet atŔķirÄ«gs UNION:

WITH RECURSIVE T AS(
  (...) -- #1
UNION ALL
  (...) -- #2, тут ŠŗŠ¾Š½Ń‡Š°ŠµŃ‚ся Š³ŠµŠ½ŠµŃ€Š°Ń†Šøя стŠ°Ń€Ń‚Š¾Š²Š¾Š³Š¾ сŠ¾ŃŃ‚Š¾ŃŠ½Šøя рŠµŠŗурсŠøŠø
UNION ALL
  (...) -- #3, тŠ¾Š»ŃŒŠŗŠ¾ этŠ¾Ń‚ Š±Š»Š¾Šŗ рŠµŠŗурсŠøŠ²Š½Ń‹Š¹ Šø Š¼Š¾Š¶ŠµŃ‚ сŠ¾Š“ŠµŃ€Š¶Š°Ń‚ŃŒ Š¾Š±Ń€Š°Ń‰ŠµŠ½ŠøŠµ Šŗ T
)
...

Jums arÄ« jāspēj "izcelt" Ŕādus piemērus. Å ajā piemērā mēs to redzam UNION-MÅ«su pieprasÄ«jumā bija 3 segmenti. AttiecÄ«gi viens UNION atbilst Append-mezgls un uz otru - Recursive Union.
PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu

LasīŔanas-rakstīŔanas dati

Viss ir izkārtots, tagad mēs zinām, kura pieprasÄ«juma daļa atbilst kādam plānam. Un Å”ajos gabalos mēs varam viegli un dabiski atrast tos objektus, kas ir ā€œlasāmiā€.

No vaicājuma viedokļa mēs nezinām, vai tā ir tabula vai CTE, bet tos apzÄ«mē viens un tas pats mezgls RangeVar. Un attiecÄ«bā uz ā€œlasāmÄ«buā€ tas ir arÄ« diezgan ierobežots mezglu kopums:

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

Mēs zinām plāna un vaicājuma struktūru, zinām bloku atbilstību, zinām objektu nosaukumus - veicam salīdzinājumu viens pret vienu.
PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu

Atkal uzdevums "ar zvaigznÄ«ti". Mēs pieņemam pieprasÄ«jumu, izpildām to, mums nav pseidonÄ«mu ā€” mēs to vienkārÅ”i divreiz nolasām no tā paÅ”a CTE.
PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu

Skatāmies plānu ā€“ kāda ir problēma? Kāpēc mums bija aizstājvārds? Mēs to nepasÅ«tÄ«jām. Kur viņŔ dabÅ« tādu ā€œskaitļa numuruā€?

PostgreSQL to pievieno pats. Jums tas tikai jāsaprot tikai tāds pseidonÄ«ms mums, salÄ«dzināŔanas nolÅ«kos ar plānu, nav nekādas jēgas, te vienkārÅ”i pievienots. NepievērsÄ«sim viņam uzmanÄ«bu.

Otrais uzdevums "ar zvaigznÄ«ti": ja mēs lasām no sadalÄ«tas tabulas, tad mēs iegÅ«sim mezglu Append vai Merge Append, kas sastāvēs no liela skaita ā€œbērnuā€, un katrs no tiem bÅ«s kaut kā Scan'om no tabulas sadaļas: Seq Scan, Bitmap Heap Scan vai Index Scan. Bet jebkurā gadÄ«jumā Å”ie ā€œbērniā€ nebÅ«s sarežģīti vaicājumi - Ŕādi var atŔķirt Å”os mezglus no Append pie UNION.
PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu

Mēs arÄ« saprotam Ŕādus mezglus, savācam tos ā€œvienā kaudzēā€ un sakām: ā€œviss, ko jÅ«s lasāt no megatable, ir Å”eit un lejā".

"VienkārÅ”i" datu saņemÅ”anas mezgli

PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu

Values Scan atbilst plānā VALUES pieprasījumā.

Result ir pieprasījums bez FROM kā SELECT 1. Vai arī, ja jums ir apzināti nepatiesa izteiksme WHERE-bloks (pēc tam parādās atribūts 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 ā€œkartiā€ uz tāda paÅ”a nosaukuma SRF.

Bet ar ligzdotajiem vaicājumiem viss ir sarežģītāk - diemžēl tie ne vienmēr pārvērÅ”as par InitPlan/SubPlan. Dažreiz tie pārvērÅ”as par ... Join vai ... Anti Join, it Ä«paÅ”i, ja rakstāt kaut ko lÄ«dzÄ«gu WHERE NOT EXISTS .... Un Å”eit ne vienmēr ir iespējams tos apvienot - plāna tekstā nav operatoru, kas atbilst plāna mezgliem.

Atkal uzdevums "ar zvaigznīti": daži VALUES pieprasījumā. Šajā gadījumā un plānā jūs iegūsit vairākus mezglus Values Scan.
PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu

ā€œNumurētieā€ sufiksi palÄ«dzēs tos atŔķirt vienu no otra - tie tiek pievienoti tieÅ”i tādā secÄ«bā, kādā tiek atrasti atbilstoÅ”ie. VALUES-bloki gar pieprasÄ«jumu no augÅ”as uz leju.

Datu apstrāde

Å Ä·iet, ka viss mÅ«su pieprasÄ«jumā ir sakārtots ā€” atliek tikai Limit.
PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu

Bet Å”eit viss ir vienkārÅ”s - tādi mezgli kā Limit, Sort, Aggregate, WindowAgg, Unique ā€œkartētā€ viens pret vienu ar atbilstoÅ”iem pieprasÄ«juma operatoriem, ja tādi ir. Å eit nav ā€œzvaigžņuā€ vai grÅ«tÄ«bu.
PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu

PIEVIENOJIES

Grūtības rodas, ja vēlamies apvienoties JOIN savā starpā. Tas ne vienmēr ir iespējams, bet tas ir iespējams.
PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu

No vaicājumu parsētāja viedokļa mums ir mezgls JoinExpr, kurai ir tieÅ”i divi bērni - kreisi un labais. Tas attiecÄ«gi ir ā€œvirsā€ jÅ«su JOIN un kas ir rakstÄ«ts ā€œzemā€ pieprasÄ«jumā.

Un no plāna viedokļa tie ir divi dažu pēcteči * Loop/* Join-mezgls. Nested Loop, Hash Anti Join,... - kaut kas tamlīdzīgs.

Izmantosim vienkārÅ”u loÄ£iku: ja mums ir tabulas A un B, kas plānā ā€œsavienojasā€ viena otrai, tad pieprasÄ«jumā tās varētu atrasties vai nu A-JOIN-BVai B-JOIN-A. Mēģināsim apvienot Ŕādi, mēģināsim apvienot otrādi, un tā tālāk, lÄ«dz mums beigsies Ŕādi pāri.

Ņemsim mūsu sintakses koku, paņemsim savu plānu, paskatīsimies uz viņiem... nav līdzīgi!
PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu

Pārzīmēsim to grafiku veidā - ak, tas jau kaut kā izskatās!
PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu

AtzÄ«mēsim, ka mums ir mezgli, kuros vienlaikus ir bērni B un C ā€“ mums ir vienalga, kādā secÄ«bā. Apvienosim tos un apgriezÄ«sim mezgla attēlu otrādi.
PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu

PaskatÄ«simies vēlreiz. Tagad mums ir mezgli ar bērniem A un pāriem (B + C) ā€” saderÄ«gi arÄ« ar tiem.
PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu

Lieliski! Izrādās, mēs esam Å”ie divi JOIN no pieprasÄ«juma ar plānu mezgli tika veiksmÄ«gi apvienoti.

Diemžēl Ŕī problēma ne vienmēr tiek atrisināta.
PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu

Piemēram, ja pieprasÄ«jumā A JOIN B JOIN C, un plānā, pirmkārt, tika savienoti ā€œÄrējieā€ mezgli A un C. Bet pieprasÄ«jumā tāda operatora nav, mums nav ko izcelt, nav kam pievienot mājienu. Tāpat ir ar "komatu", kad rakstāt A, B.

Bet vairumā gadÄ«jumu gandrÄ«z visus mezglus var ā€œatsaistÄ«tā€, un jÅ«s varat savlaicÄ«gi iegÅ«t Ŕāda veida profilÄ“Å”anu kreisajā pusē - burtiski, piemēram, pārlÅ«kā Google Chrome, analizējot JavaScript kodu. Varat redzēt, cik ilgs laiks bija nepiecieÅ”ams katras rindas un katra priekÅ”raksta izpildei.
PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu

Un, lai jums bÅ«tu ērtāk to visu izmantot, esam izveidojuÅ”i krātuvi arhÄ«vs, kur varat saglabāt un vēlāk atrast savus plānus kopā ar saistÄ«tajiem pieprasÄ«jumiem vai kopÄ«got saiti ar kādu.

Ja jums vienkārŔi nepiecieŔams ievietot nelasāmu vaicājumu atbilstoŔā formā, izmantojiet mūsu "normalizators".

PostgreSQL vaicājumu profilētājs: kā saskaņot plānu un vaicājumu

Avots: www.habr.com

Pievieno komentāru