Shumë nga ata që tashmë po e përdorin — shërbimin tonë të vizualizimit të planeve PostgreSQL, ndoshta nuk e dinë një nga superfuqitë e tij — të shndërrojë një pjesë të logut të serverit që është e vështirë për t'u lexuar...

... në një kërkesë të përgatitur bukur me këshilla kontekstuale për nyjet përkatëse të planit:

Në këtë interpretim të pjesës së dytë të raportit tim unë do të flas për mënyrën si arritëm ta bëjmë këtë.
Me transkriptin e pjesës së parë, e cila ishte e dedikuar problemeve tipike të performancës së kërkesave dhe zgjidhjeve të tyre, mund të njiheni në artikullin .

Fillimisht do të merremi me ngjyrat — dhe do të ngjyrosim jo planin, sepse ne e kemi ngjyrosur atë, tashmë është i bukur dhe i kuptueshëm, por do të merremi me kërkesën.
Na dukej se një «tepih» i tillë i paformatizuar që është nxjerrë nga logu duket shumë i shëmtuar dhe prandaj — i pakëndshëm.

Veçanërisht kur zhvilluesit në kod «bashkojnë» trupin e kërkesës (kjo, sigurisht, është një antipattern, por ndodh) në një rresht. Tmerre!
Le ta vizatojmë këtë më bukur.

Nëse arrijmë ta vizatojmë bukur këtë, domethënë, të analizojmë dhe riparojmë trupin e pyetjes, atëherë mund të ngjitim një sugjerim për çdo objekt të kësaj pyetjeje — se çfarë ndodhi në pikën përkatëse të planit.
Pema sintaksore e pyetjes
Për ta bërë këtë, së pari duhet të analizojmë pyetjen.

Duke qenë se ne , kemi krijuar një modul për të, mund ta . Në të vërtetë, kjo janë binding të zgjeruara për brendësitë e parserit të PostgreSQL. Domethënë, është thjesht një gramatikë e përkthyer në format binar dhe janë krijuar binding me NodeJS. Ne përdorëm module të huaja si bazë — nuk ka asnjë sekret të madh këtu.
Futur trupin e pyetjes në funksionin tonë — në daljen marrim pemën sintaksore të analizuara në formën e një objekti JSON.

Tani, mbi këtë pemë mund të kalojmë në anën tjetër dhe të rindërtojmë pyetjen me ato hapësira, ngjyrimin, formatimin që dëshirojmë. Jo, kjo nuk është e konfiguruar, por na duket se kështu do të ishte e përshtatshme.

Krahasimi i nyjeve të pyetjes dhe planit
Tani do ta shohim se si mund të kombinojmë planin që ne e shqyrtuam në hapin e parë dhe kërkesën që ne e shqyrtuam në hapin e dytë.
Le të marrim një shembull të thjeshtë — kemi një kërkesë që formon CTE dhe e lexon dy herë nga ajo. Ai gjeneron një plan të tillë.

CTE
Nëse i hedhim një vështrim të afërt, që nga versioni 12 (ose duke filluar nga ai me fjalën kyçe MATERIALIZED) formimi .

Pra, nëse shohim diku në kërkesë gjenerimin e CTE dhe diku në plan një nyje CTE, atëherë këto nyje ndërlidhen me njëra-tjetrën dhe ne mund t'i kombinojmë ato menjëherë.
Detyra "me yll": CTE mund të jenë të ngjitura.

Janë disa që janë shumë keq të ngjitura, madje edhe të emërtuara njësoj. Për shembull, ju mund të ndani CTE A të bëni CTE X, dhe në të njëjtin nivel brenda CTE B të bëni përsëri CTE X:
ME A SI (
ME X SI (...)
Zgjedh ...
)
, B SI (
ME X SI (...)
Zgjedh ...
)
...Kur krahasoni, duhet ta kuptoni këtë. Ta kuptoni me "sytë" — edhe duke parë planin, edhe duke parë trupin e kërkesës — është shumë e vështirë. Nëse keni një gjenerim të CTE të ndërlikuar, të ngjitur, kërkesat janë të mëdha — atëherë ndjenja është krejtësisht e pavetëdijshme.
UNION
Nëse kemi një fjalë kyçe në kërkesë UNION [ALL] (operatori i lidhjes midis dy grupeve), atëherë ai në plan i korrespondon ose një nyje Shto, ose ndonjë Bashkimi Rekursiv.

Ajo që "sipër" mbi UNION — është pasardhësi i parë i nyjës tonë, ndërsa "poshtë" — i dyti. Nëse përmes UNION kemi "në ngjitje" disa blloqe menjëherë, atëherë Shto-nyja do të mbetet vetëm një, por fëmijët e saj do të jenë jo dy, por shumë — në rendin siç vijnë, përkatësisht:
(...) -- #1
UNION ALL
(...) -- #2
UNION ALL
(...) -- #3Shto
-> ... #1
-> ... #2
-> ... #3
Detyra "me yll": brenda gjenerimit të zgjedhjes rekursive (WITH RECURSIVE) gjithashtu mund të ketë më shumë se një UNION. Por gjithmonë rekursive është vetëm blloku më i fundit pas të fundit UNION. Çdo gjë që është lart — është një, por tjetër UNION:
ME RECURSIVE T AS(
(...) -- #1
UNION ALL
(...) -- #2, këtu përfundon gjenerimi i gjendjes fillestare të rekursioneve
UNION ALL
(...) -- #3, vetëm ky bllok është rekursiv dhe mund të përmbajë referenca te T
)
... Këto shembuj duhet gjithashtu të dinë "të ngjiten". Në këtë shembull ne shohim se UNION-segmenteve në kërkesën tonë ishin 3 copa. Përkatësisht, njëri UNION përputhet Shto-nyjë, ndërsa tjetri — Bashkimi Rekursiv.

Leximi-shkrimi i të dhënave
Të gjitha, e organizuam, tani e dimë se cili pjesë e kërkesës i përket cilit pjesë të planit. Dhe në këto pjesë ne mund të gjejmë lehtësisht objektet që "lexohen".
Nga këndvështrimi i kërkesës ne nuk e dimë — është tabelë apo CTE, por ato karakterizohen nga e njëjta nyje. RangeVar. Ndërsa në planin "lexohet" — gjithashtu është një grup mjaft të kufizuar nyjesh:
Skemë e sekuencës në [tbl]Skani i Bitmap Heap në [tbl]Indeksi [Vetëm] Skano [Mbrapsht] duke përdorur [idx] në [tbl]Skano CTE në [cte]Futni/Update/Fshi në [tbl]
Struktura e planit dhe e kërkesës e dimë, përputhja e blloqeve e dimë, emrat e objekteve i dimë — bëjmë një përputhje të qartë.

Përsëri, detyra "me yll". Marrim kërkesën, e ekzekutojmë, nuk kemi asnjë alias — ne thjesht lexuam dy herë nga një CTE.

Shikojmë në plan — çfarë ndodhi? Pse na doli aliasi? Ne nuk e kërkuam. Nga erdhi ky "numer"?
PostgreSQL e shton atë vetë. Duhet thjesht të kuptojmë se pikërisht ky alias për ne për qëllimet e përputhjes me planin nuk ka asnjë kuptim, ai është thjesht këtu i shtuar. Mos ta vëmë re atë.
E dyta detyra "me yll": nëse kemi lexim nga një tabelë të ndarë, atëherë do të marrim një nyjë Shto ose Bashko Shto, e cila do të përbëhet nga një numër i madh "fëmijësh", dhe secila do të jetë një Scan' nga tabela-seksion: Skemë e sekuencës, Skani i Bitmap Heap ose Index Scan. Por megjithatë, këto "fëmijë" do të jenë kërkesa të thjeshta - kështu që këto nyje mund të dallohen nga Shto në UNION.

Këto nyje ne i kuptojmë gjithashtu, i grupojmë "në një grumbull" dhe themi: "gjithçka që ke lexuar nga megatable - është këtu dhe poshtë në pemë".
"Nyjet e thjeshta" për marrjen e të dhënave

Skano të Dhënash përputhet me VALUES në kërkesë.
Shkalla - kjo është një kërkesë pa FROM si SELECT 1. Ose kur ke një shprehje të gënjyer në WHERE-bllok (atëherë krijohet atributi One-Time Filter):
EXPLAIN ANALYZE
SELECT * FROM pg_class WHERE FALSE; -- ose 0 = 1Rezultati (cost=0.00..0.00 rows=0 width=230) (actual time=0.000..0.000 rows=0 loops=1)
One-Time Filter: false
Skano Funksional "mapojen" në SRF të njëjtë.
Por për kërkesat e brendshme gjërat janë më të komplikuara - fatkeqësisht, ato nuk transformohen gjithmonë në PlaniFill/SubPlani. Herë pas here ato kthehen në ... Join ose ... Anti Join, sidomos kur shkruani diçka si WHERE NOT EXISTS .... Dhe aty kombinimi nuk është gjithmonë i mundur - në tekstin e planit nuk ka nyje përkatëse të operatorëve.
Përsëri, detyra "me yll": disa VALUES në kërkesë. Në këtë rast, dhe në plan do të merrni disa nyje Skano të Dhënash.

Dallimi i tyre do të ndihmohet nga sufiksat "numërorë" - ai shtohet saktësisht në rendin e gjetjes së VALUES-bllokëve gjatë kërkesës nga lartë poshtë.
Përpunimi i të dhënave
Duket se kemi trajtuar gjithçka në kërkesën tonë — ka mbetur vetëm Kufizim.

Por këtu gjithçka është e thjeshtë — nyjet si Kufizim, Rendit, Agregoni, WindowAgg, Unike «mapohet» një-për-një me operatorët përkatës në kërkesë, nëse ata janë aty. Nuk ka asnjë «yll» dhe as vështirësi.

JOIN
Vështirësitë ndodhin kur duam të kombinojmë JOIN me njëri-tjetrin. Kjo nuk bëhet gjithmonë, por është e mundur.

Nga perspektiva e parserit të kërkesës, kemi një nyje BashkohuExpr, e cila ka pikërisht dy pasardhës — të majtë dhe të djathtë. Kjo, përkatësisht, është ajo që është «mbi» JOIN tuaj dhe ajo që është «në» të shkruarit në kërkesë.
Dhe nga perspektiva e planit, kjo janë dy pasardhës të një * Rreth/* Bashkëngjitu-nyje. Nested Loop, Hash Anti Join,… — diçka e tillë.
Të shfrytëzojmë logjikën e thjeshtë: nëse kemi tabelat A dhe B, të cilat «bashkohen» me njëra-tjetrën në plan, atëherë ato mund të kenë qenë të vendosura në kërkesë si A-JOIN-B, ose B-JOIN-A. Le t'i kombinojmë kështu, le t'i kombinojmë për ndryshe, dhe kështu deri sa të përfundojnë këto çifte.
Të marrim pemën tonë sintaksore, të marrim planin tonë, t'i shohim ato… nuk duket!

Ta vizatojmë atë në formë grafikësh — o, tashmë duket diçka që i ngjan diçkaje!

Le të vëmë re se kemi nodet që kanë njëkohësisht fëmijë B dhe C — nuk ka rëndësi në cilin rend. Do t'i bashkojmë dhe do ta kthejmë imazhin e nodit.

Të shohim përsëri. Tani kemi nodet me fëmijë A dhe çiftet (B + C) — do t'i bashkojmë edhe këta.

Shkëlqyeshëm! Del se këto dy JOIN në kërkesat me nodet e planit i kemi bashkuar me sukses.
Fatkeqësisht, kjo detyrë nuk zgjidhet gjithmonë.

Për shembull, nëse në kërkesë A JOIN B JOIN C, dhe në plan iu bashkuan në radhë nodet "ekstreme" A dhe C. Dhe në kërkesë nuk ka një operator të tillë, nuk kemi asgjë për të theksuar, nuk ka për çfarë të lidhim sugjerimin. E njëjta gjë ndodh me "presjen", kur shkruani A, B.
Por, në shumicën e rasteve, pothuajse të gjitha nodet arrijnë të "zgjidhen" dhe të marrin këtë profilim në të majtë sipas kohës — dosido, si në Google Chrome kur analizoni kodin në JavaScript. Shihni sa kohë e kishte çdo rresht dhe çdo operator "ekzekutuar".

Dhe për ta bërë më të lehtë përdorimin tuaj me të gjitha këto, ne kemi bërë ruajtje , ku mund të ruani dhe më pas të gjeni planet tuaja së bashku me kërkesat e asociuara ose të ndani një lidhje me dikë.
Nëse thjesht duhet ta çoni një kërkesë të paqartë në një formë të arsyeshme, përdorni .

Burimi: habr.com
