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...
... skaisti izstrÄdÄtÄ vaicÄjumÄ ar kontekstuÄliem padomiem attiecÄ«gajiem plÄna mezgliem:
Å 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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
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.
ā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.
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.
PIEVIENOJIES
GrÅ«tÄ«bas rodas, ja vÄlamies apvienoties JOIN savÄ starpÄ. Tas ne vienmÄr ir iespÄjams, bet tas ir iespÄjams.
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!
PÄrzÄ«mÄsim to grafiku veidÄ - ak, tas jau kaut kÄ izskatÄs!
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.
PaskatÄ«simies vÄlreiz. Tagad mums ir mezgli ar bÄrniem A un pÄriem (B + C) ā saderÄ«gi arÄ« ar tiem.
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.
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.
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.