Dekodimi i raportit të vitit 2020 nga Bruce Momjian "Zhbllokimi i Menaxherit të Ndërtimeve Postgres".

(Vini re: Të gjitha kërkesat SQL nga diapozitivët mund të merren nga ky lidhje: )
Përshëndetje! Është shkëlqyer të jem sërish këtu në Rusi. Më vjen keq që nuk arrita të vij vitin e kaluar, por këtë vit Ivan dhe unë kemi plane të mëdha. Shpresoj se do të jem këtu shumë më shpesh. Më pëlqen shumë të vij në Rusi. Do të vizitoj Tyumen dhe Tver. Jam shumë i lumtur që do të kem mundësinë të jem në këto qytete.
Unë quhem Bruce Momjian. Punoj në EnterpriseDB dhe kam punuar me Postgres për më shumë se 23 vjet. Jetoj në Filadelfia, SHBA. Udhtoj rreth 90 ditë në vit dhe marr pjesë në rreth 40 konferenca. Web faqja ime Isha më parë një mësues, profesor para se të filloja punën me Postgres. Jam shumë i emocionuar që do të kem mundësinë t'ju flas për atë që do t'ju tregoj tani. Kjo është një nga prezantimet më interesante që kam. Dhe kjo prezantim ka 110 diapozitiva. Do të fillojmë me gjëra të thjeshta, e më pas prezantimi do të bëhet gjithnjë e më kompleks.
Ky është një bisedë e mërzitshme. Bllokimi nuk është një temë shumë popullore. Ne duam që ajo të zhduket. Është si të shkelesh te stomatologu.

Bllokimi është një problem për shumë njerëz që punojnë me bazat e të dhënave dhe kanë disa procese që funksionojnë njëkohësisht. Ata kanë nevojë për bllokim. Pra, sot do t'ju jap baza mbi bllokimin.

- Identifikuesit e transaksioneve. Kjo është një pjesë e mërzitshme e prezantimit, por është e nevojshme ta kuptoni.
- Më pas do të flasim për llojet e bllokimeve. Kjo është një pjesë mjaft mekanike.
- Dhe më pas do të japim disa shembuj bllokimesh. Kjo do të jetë e ndërlikuar për t'u kuptuar.
- Le të flasim për bllokimet.

Terminologjia është mjaft komplekse. Sa prej jush e dinë se nga vjen ky fragment? Dy njerëz. Ky është nga një lojë që quhet "Krahu i Madh i Ndihmës". Ishte një lojë kompjuterike tekstuale në vitet '80, më duket. Duhej të hyje në një shpella, në një labirint dhe teksti ndryshonte, megjithatë përmbajtja ishte përafërsisht e njëjtë çdo herë. Kështu e mbaj mend këtë lojë.

Dhe këtu shohim emrat e bllokimeve që na vijnë nga Oracle. Ne i përdorim ato.

Këtu shohim terma që më ngatërrojnë. Për shembull, SHARE UPDATE EXCLUSIVE. Më pas SHARE RAW EXCLUSIVE. Për të qenë të sinqertë, këto emra nuk janë shumë të qartë. Do të përpiqemi t'i shqyrtojmë ato më në detaje. Disa përmbajnë fjalën "share", që do të thotë – të ndahesh. Disa përmbajnë fjalën "exclusive" — ekskluzive. Disa përmbajnë të dyja. Do të doja të filloja me mënyrën se si funksionojnë këto bllokime.

Gjithashtu është shumë e rëndësishme fjala "qasje" — access. Dhe fjalët "row" — rresht. Pra, ndarja e qasjes, ndarja e rreshtave.

Një problem tjetër që duhet të kuptohet në Postgres, për fat të keq, nuk do të mund t'iu flas për të në prezantimin tim, është MVCC. Kam një prezantim të veçantë mbi këtë temë në web faqen time. Dhe nëse mendoni se kjo prezantim është e ndërlikuar, atëherë MVCC është ndoshta e mia më e ndërlikuar. Dhe nëse jeni të interesuar, mund ta shikoni në faqe. Mund të shikoni videon.

Një tjetër moment që duhet të kuptojmë – janë identifikuesit e transaksionit. Shumë transaksione nuk mund të funksionojnë pa identifikues unik. Dhe këtu kemi një shpjegim se çfarë është një transaksion. Në Postgres ka dy sisteme numërimi të transaksioneve. E di, kjo nuk është një zgjidhje shumë elegante.

Gjithashtu mbani në mend se diapozitivët do të jenë mjaft të ndërlikuar për t'u kuptuar, kështu që e rëndësishme është të kushtoni vëmendje asaj që është theksuar me ngjyrë të kuqe.

Shikoni. Me ngjyrë të kuqe është theksuar numri i transaksionit. Aty tregohen funksioni SELECT pg_back. Ai kthen transaksionin tim dhe ID-në e këtij transaksioni.

Një moment tjetër, nëse ju pëlqen kjo prezantim dhe dëshironi ta ekzekutoni në bazën tuaj të të dhënave, atëherë mund të shkoni në këtë lidhje, të theksuar me ngjyrë të rozë, dhe të shkarkoni SQL për këtë prezantim. Dhe mund ta ekzekutoni thjesht në PSQL tuaj dhe të gjithë prezantimi do të shfaqet në ekranin tuaj menjëherë. Nuk do të ketë ngjyrash, por së paku do ta shihni.
Një pikë tjetër është, nëse ju pëlqeu kjo prezentim dhe dëshironi ta lançoni atë në bazën tuaj të të dhënave, mund të kaloni përmes kësaj lidhjeje të ndriçuar me ngjyrë rozë dhe të shkarkoni SQL për këtë prezentim. Dhe mund ta drejtoni thjesht në PSQL tuaj dhe e gjithë prezantimi do të shfaqet në ekranin tuaj menjëherë. Nuk do të përmbajë ngjyra, por së paku do ta shohim.

Në këtë rast, ne shohim ID-në e transaksionit. Ky është numri që i kemi caktuar. Ka gjithashtu një lloj tjetër ID të transaksionit në Postgres, i quajtur ID virtual i transaksionit.
Dhe ne duhet ta kuptojmë këtë. Është shumë e rëndësishme, përndryshe nuk do të mund të kuptojmë bllokimin në Postgres.
ID virtual i transaksionit është ID e transaksionit që nuk përmban vlera të përhershme. Për shembull, nëse ekzekutoj komandën SELECT, ndoshta nuk do të ndryshoj bazën e të dhënave, nuk do të bllokoj asgjë. Prandaj, kur ekzekutojmë një SELECT të thjeshtë, ne nuk i japim kësaj transaksioni një ID të përhershme. Aty i japim vetëm një ID virtual.
Dhe kjo rrit performancën e Postgres, përmirëson mundësitë për pastrim, prandaj, ID-ja virtuale e transaksionit përbëhet nga dy numra. Numri i parë para shenjës së ndarjes është ID e backend-it. Ndërsa në të djathtë shohim thjesht një numër counting.

Prandaj, nëse ekzekutoj një pyetje, ajo thotë se ID backend është 2.

Nëse ekzekutoj një seri të tillë transaksionesh, atëherë shohim se numri counting rritet çdo herë kur ekzekutoj një pyetje. Për shembuj, kur ekzekutoj pyetje 2/10, 2/11, 2/12 etj.

Mbani mend se këtu ka dy kolona. Në të majtë ne shohim ID virtual të transaksionit – 2/12. Ndërsa në të djathtë kemi ID-në e përhershme të transaksionit. Dhe ky fushë është bosh. Dhe ky transaksion nuk modifikon bazën e të dhënave. Prandaj nuk i caktohem një ID të përhershme transaksioni.

Sapo të ekzekutoj komandën ANALYZE, e njëjta pyetje më jep një ID të përhershme të transaksionit. Shikoni se si ka ndryshuar kjo. Më parë nuk e kisha këtë ID, tani ka dalë.

Pra, këtu është një pyetje tjetër, një transaksion tjetër. Numri virtual i transaksionit është 2/13. Dhe nëse kërkoj ID-në e përhershme të transaksionit, atëherë kur ekzekutoj pyetjen, do ta marr.

Pra, përsëri. Ne kemi ID virtual të transaksionit dhe ID të përhershme të transaksionit. Thjesht kuptoni këtë për të kuptuar sjelljen e Postgres.

Kemi kaluar në seksionin e tretë. Këtu do të kalojmë përmes llojeve të ndryshme të bllokimeve në Postgres. Kjo nuk është shumë emocionuese. Seksioni i fundit do të jetë shumë më interesant. Por ne duhet të shqyrtojmë gjërat bazike, përndryshe nuk do të kuptojmë atë që do të vijë më pas.
Do të kalojmë nëpër këtë seksion, do të shohim çdo lloj bllokimi. Dhe unë do t'ju tregoj shembuj se si vendosen, si funksionojnë, do t'ju tregoj disa pyetje që mund të përdorni për të parë si funksionon bllokimi në Postgres.

Për të krijuar një pyetje dhe për të parë se çfarë ndodh në Postgres, na nevojitet të lëshojmë një pyetje në view të sistemit. Në këtë rast, ngjyra e kuqe na thekson pg_lock. Pg_lock është një tabelë sistemike që na thotë se cilat bllokime po përdoren aktualisht në Postgres.
Megjithatë, është shumë e vështirë për mua të tregoj pg_lock si vetë, sepse është mjaft e komplikuar. Prandaj kam krijuar një view që tregon pg_locks. Dhe ai gjithashtu kryen disa punë për mua, e cila më ndihmon të kuptoj më mirë. Do thotë, ai përjashton bllokimet e mia, sesionin tim etj. Kjo është thjesht SQL standard dhe ai do t'ju lejojë të tregoni më mirë se çfarë ndodh.

Një tjetër problem është se ky view është shumë i gjerë, prandaj duhet të krijoj një të dytë – lockview2.
Dhe ai më tregon edhe kolona të tjera nga tabela. Dhe një tjetër që më tregon kolona të tjera. Kjo është mjaft e komplikuar, prandaj kam përpjekur ta paraqes sa më thjesh që është e mundur.

Pra, krijuam një tabelë të quajtur Lockdemo. Dhe kemi krijuar një rresht aty. Ky është tabela jonë shembuj. Dhe ne do të krijojmë seksione për të treguar shembuj bllokimesh.

Pra, një rresht, një kolonë. Lloji i parë i bllokimit quhet ACCESS SHARE. Ky është bllokimi më pak ndaluese. Kjo do të thotë se ai pothuajse nuk përplaset me bllokime të tjera.
Dhe nëse duam ta përcaktojmë bllokimin në mënyrë eksplicite, ne ekzekutojmë komandën "lock table". Dhe ajo do ta bllokojë në mënyrë eksplicite, do thotë, në mënyrë ACCESS SHARE ne ekzekutojmë lock table. Dhe nëse unë e ekzekutoj PSQL në sfond, atëherë unë ekzekutoj kështu një sesion të dytë nga sesioni im i parë. Do thotë, çfarë do të bëj këtu? Unë kaloj në një sesion tjetër dhe i them "trego lockview për këtë pyetje". Dhe këtu kam AccessShareLock në këtë tabelë. Kjo është pikërisht ajo që kërkova. Dhe ai thotë se bllokimi iu caktua. Shumë e thjeshtë.

Më pas, nëse shohim në kolonën e dytë, nuk ka asgjë. Ato janë bosh.

Dhe nëse ekzekutoj komandën "SELECT", kjo është një mënyrë e implicit (shfaqur) për të kërkuar AccessShareLock. Pra, lëshoj tabelën time dhe ekzekutoj pyetjen, dhe pyetja kthen disa rreshta. Dhe në një nga rreshtat shohim AccessShareLock. Pra, SELECT shkakton AccessShareLock në tabelë. Dhe nuk përplaset pothuajse me asgjë, sepse është bllokim i nivelit të ulët.

Çfarë ndodh nëse unë ekzekutoj një SELECT dhe kam tre tabela të ndryshme? Më parë kam ekzekutuar vetëm një tabelë, tani po ekzekutoj tre: pg_class, pg_namespace dhe pg_attribute.

Dhe tani, kur e shqyrtoj kërkesën, shoh 9 AccessShareLocks në tre tabelat. Pse? Me ngjyrë blu janë të theksuara tre tabelat: pg_attribute, pg_class, pg_namespace. Por gjithashtu mund të shihni se të gjitha indekset që janë të përcaktuara përmes këtyre tabelave gjithashtu kanë AccessShareLock.
Dhe kjo është një bllokim që praktikisht nuk shkakton konflikte me të tjerët. Të gjitha që bën është se nuk lejon që ne të heqim tabelën derisa po e zgjedhim. Kjo ka kuptim. D.m.th., nëse ne zgjedhim një tabelë dhe ajo në atë moment zhduket, kjo është e gabuar, prandaj AccessShare është një bllokim i nivelit të ulët që na thotë "mos e fshini këtë tabelë, derisa të punoj".. Në thelb, kjo është gjithçka që bën.

ROW SHARE është një bllokim paksa ndryshe.

Le ta marrim një shembull. SELECT ROW SHARE është mënyra e bllokimit të çdo rreshti veç e veç.Kështu askush nuk mund t’i fshijë apo ndryshojë ata derisa ne t’i shohim.
Pra, çfarë bën SHARE LOCK? Ne shohim që ID e transaksionit 681 për SELECT. Dhe kjo është interesante. Çfarë ndodhi këtu? Herën e parë ne shohim një numër në fushën "Lock". Ne marrim ID e transaksionit dhe ai thotë se bllokon atë në mënyrë ekskluzive. E gjithë çfarë bën, është se thotë se kam një rresht që teknalisht është bllokuar diku në tabelë. Por nuk thotë se ku saktësisht. Më vonë do ta shqyrtojmë këtë më në detaje.

Këtu ne po flasim se bllokimi përdoret nga ne.

Pra, bllokimi ekskluziv në mënyrë eksplicite thotë se është ekskluziv. Dhe gjithashtu, nëse fshini një rresht në këtë tabelë, kjo edhe do të ndodhë, siç mund ta shihni.

SHARE EXCLUSIVE është një bllokim më i gjatë.

Kjo është komanda (ANALYZE) e analizuesit që do të përdoret.

SHARE LOCK - ju mund ta bllokoni në mënyrë eksplicite në modën share.

Ju gjithashtu mund të krijoni një indeks unik. Dhe atje mund ta shihni SHARE LOCK, që është pjesë e tyre. Dhe ai bllokon tabelën dhe vendos mbi të një bllokim SHARE LOCK.
Për default, SHARE LOCK në tabelë do të thotë se njerëzit e tjerë mund ta lexojnë tabelën, por askush nuk mund ta modifikojë. Dhe kjo ndodh kur krijoni një indeks unik.
Nëse krijoj një indeks unik me kohë, do të kem një lloj tjetër bllokimi, sepse, siç e mbani mend, përdorimi i indekseve me kohë ul kërkesat për bllokim. Dhe nëse përdor bllokimin normal, indeksi normal, atëherë në këtë mënyrë parandaloj regjistrimin në indeksin e tabelës gjatë krijimit të tij. Nëse përdor indeksin me kohë, atëherë duhet të përdor një lloj tjetër bllokimi.

SHARE ROW EXCLUSIVE - përsëri, mund të vendoset në mënyrë eksplicite.

Ose mund të krijojmë një rregull, d.m.th. të marrim ndonjë rast të caktuar, në të cilin ajo do të përdoret.

Bllokimi EKSKLUZIV do të thotë se askush tjetër nuk mund ta ndryshojë tabelën.

Këtu shohim lloje të ndryshme bllokimesh.

ACCESS EXCLUSIVE, për shembull, është komanda bllokuese. Për shembull, nëse bëni CLUSTER table, kjo do të thotë se askush nuk do të mund të shkruajë aty. Dhe bllokon jo vetëm tabelën, por edhe indekset gjithashtu.

Kjo është faqja e dytë e bllokimit ACCESS EXCLUSIVE, ku ne shohim saktësisht se çfarë bllokon në tabelë. Ajo bllokon rreshta të veçantë në tabelë, që është mjaft interesante.
Kjo është gjithë informacioni bazik që doja të jepja. Flisnim për bllokimet, për ID e transaksioneve, flisnim për ID e transaksioneve virtuale, për ID e transaksioneve të përhershme.

Dhe tani ne do të shohim shembujt e bllokimeve. Kjo është pjesa më interesante. Do të shohim raste shumë interesante. Dhe detyra ime në këtë prezantim është t'ju jap një pasqyrë më të mirë se çfarë bën Postgres në të vërtetë kur përpiqet të bllokojë gjëra të ndryshme. Më duket se ai e bën shumë mirë bllokimin e pjesëve të veçanta.
Le të shqyrtojmë disa shembuj.

Ne do të fillojmë me tabelat dhe me një rresht në tabelë. Kur unë fus diçka, më shfaqet ExclusiveLock, ID e transaksionit dhe ExclusiveLock në tabelë.

Çfarë do të ndodhte nëse do të fusja edhe dy rreshta? Dhe tani në tabelën tonë kemi tre rreshta. Dhe unë fus një rresht dhe kam këtë në daljen. Dhe nëse fus edhe dy rreshta, çfarë është e çuditshme këtu? Këtu ka një çuditëri, sepse unë kam shtuar tre rreshta në këtë tabelë, por ende kam dy rreshta në bllokimin e tabelës. Dhe kjo është, në thelb, një sjellje themelore e Postgres.
Shumë njerëz mendojnë se nëse bllokoni 100 rreshta në një bazë të dhënash, do t'ju nevojiten 100 kërkesa bllokimi. Nëse bllokoj 1,000 rreshta njëherësh, atëherë do të kemi nevojë për 1,000 kërkesa të tilla. Dhe nëse dua të bllokoj një milion ose një miliard rreshta. Por nëse veprojmë kështu, do të funksionojë jo mirë. Nëse keni përdorur një sistem që krijon kërkesa bllokimi për çdo rresht të veçantë, atëherë e shihni se sa është e ndërlikuar. Sepse duhet të përcaktoni menjëherë një tabelë bllokimi që mund të vendoset, por Postgres nuk vepron ashtu.
Dhe në këtë slajd është shumë e rëndësishme që këtu shfaqet qartë se ka një sistem tjetër që punon brenda MVCC, që bllokon rreshta të veçantë. Prandaj, kur bllokoni miliarda rreshta, Postgres nuk krijon miliarda komanda të veçanta për bllokim. Dhe kjo ndikon shumë në performancë.

Por çfarë ndodh kur dua të përditësoj? Tani po përditësoj një rresht, dhe mund ta vini re se ai ka kryer menjëherë dy operacione të ndryshme. Ai ka bllokuar tabelën njëkohësisht, por ai gjithashtu ka bllokuar indeksin. Dhe i duhej të bllokonte indeksin, sepse ka kufizime unik në këtë tabelë. Dhe dëshirojmë të sigurohemi që askush të mos e ndryshojë, prandaj e bllokojmë.

Çfarë do të ndodhte nëse do të doja të përditësoja dy rreshta? Dhe shohim se ai vepron po ashtu. Ne po realizojmë dyfish më shumë përditësime, por sërish po të njëjtin numër rreshtash bllokimi.
Nëse ju intereson se si e bën Postgres këtë, duhet të dëgjoni fjalimet e mia mbi MVCC, për të kuptuar se si Postgres e etiketojnë brendshëm këta rreshta që ai ndryshon. Dhe Postgres ka një mënyrë që e bën këtë, por nuk e bën në nivelin e bllokimit të tabelave, ai e bën në një nivel më të ulët dhe më efektiv.

Por nëse dëshiroj të fshij diçka? Nëse po fshij, për shembull, një rresht dhe unë ende kam dy kërkesa bllokimi, dhe madje edhe nëse dëshiroj t'i fshij të gjitha, ato ende do të jenë atje.

Dhe, për shembull, nëse unë dëshiroj të vendos 1,000 rreshta, e pastaj të fshij ose të shtoj 1,000 rreshta, ato rreshta individualë që unë shtoj ose ndryshoj, ato nuk regjistrohen këtu. Ato regjistrohen në një nivel më të ulët brenda vetë rreshtit. Dhe gjatë fjalimit tim mbi MVCC, flas për këtë në detaje. Por është shumë e rëndësishme, kur analizoni bllokimet, të siguroheni që keni bllokim në nivel tabelash dhe që këtu nuk shihni se regjistrohen rreshta të veçantë.

Si qëndron çështja me bllokimin eksplicit?

Nëse unë klikoj "përditëso", atëherë kam dy rreshta të bllokuar. Dhe nëse i përzgjedh të gjithë dhe klikoj "përditëso në të gjithë", atëherë ende do t'i kem dy regjistrime bllokimi.

Ne nuk krijojmë regjistrime të veçanta për çdo rresht të veçantë. Sepse atëherë performanca do të bjerë, mund të ketë tepër shumë prej tyre. Dhe mund të ndodhemi në një situatë të pakëndshme.

Edhe ashtu, nëse e bëjmë në mënyrë të ndarë, mund të bëjmë për të gjithë 30 herë.

Ne rikthejmë tabelën tonë, fshijmë gjithçka, pastaj sërish vendosim një rresht.

Një njohje tjetër të sjelljes që shihni në Postgres, është një sjellje shumë e njohur dhe e dëshiruar – është që ju mund të bëni update ose select. Dhe mund ta bëni atë njëkohësisht. Dhe select nuk bllokon update dhe të njëjtën gjë ndodh në anën tjetër. Ne i themi lexuesit të mos bllokojë atë që shkruan, dhe atij që shkruan, të mos bllokojë lexuesin.
Do t'ju tregoj një shembull të kësaj. Tani do të bëj një përzgjedhje. Më pas do të bëjmë INSERT. Dhe pastaj do të mund të shihni – 694. Do të mund të shihni ID-në e transaksionit që bëri këtë vendosje. Dhe kështu funksionon.

Dhe nëse tani shikoj ID-në time të backend-it, ajo është bërë – 695.

Dhe mund të shoh se 695 shfaqet në tabelën time.

Dhe nëse bëj një përditësim këtu kështu, atëherë marr një rast tjetër. Në këtë rast 695 – është një bllokim ekskluziv, dhe përditësimi ka të njëjtin sjellje, por mes tyre nuk ndodh asnjë konflikt, që është mjaft e pazakonshme.
Dhe mund të vini re se lart – është ShareLock, dhe më poshtë – është ExclusiveLock. Dhe të dyja transaksionet u realizuan.
Dhe është e nevojshme të dëgjoni fjalimin tim mbi MVCC për të kuptuar se si ndodh kjo. Por kjo është një ilustrim se si mund të bëni këtë njëkohësisht, pra njëkohësisht të bëni SELECT dhe UPDATE.

Le të reshtim dhe të bëjmë një veprim njëherë tjetër.

Nëse përpiqeni të lëshoni njëkohësisht dy përditësime në të njëjtin rresht, atëherë do të bllokohet. Dhe mbani mend, kam thënë se lexuesi nuk bllokon atë që shkruan, dhe ai që shkruan bllokon lexuesin, por një shkruese bllokon tjetrin. Pra, nuk mund të lejojmë që dy persona të përditësojnë njëkohësisht të njëjtin rresht. Duhet të presim derisa njëri prej tyre të përfundojë.

Dhe për të ilustruar këtë, do të shoh tabelën Lockdemo. Dhe do të shikojmë një rresht. Për transaksionin 698.
E kemi përditësuar në 2. 699 – kjo është përditësimi i parë. Dhe ai ka përfunduar me sukses ose është në një transaksion të pritur dhe pret që ne ta konfirmojmë ose ta anulojmë.

Por shihni një tjetër – 2/51 – kjo është transaksioni ynë i parë, sesioni ynë i parë. 3/112 – kjo është kërkesa e dytë, që ka dalë në krye dhe ka ndryshuar këtë vlerë në 3. Dhe nëse vini re, ai i sipërm e ka bllokuar veten, që është 699. Por 3/112 nuk ofroi një bllokim. Në kolonën Lock_mode shkruhet se ai është duke pritur. Ai po pret 699. Dhe nëse shikoni, ku është 699, është më sipër. Dhe çfarë bëri sesioni i parë? Ai krijoi një bllokim ekskluziv në ID-në e tij të transaksionit. Kështu e bën Postgres. Ai bllokon ID-në e tij të transaksionit. Dhe nëse doni të prisni derisa dikush të konfirmojë ose të anulojë, duhet të prisni derisa të ketë një transaksion të pritur. Prandaj mund të shohim një rresht të çuditshëm.
Le të shohim përsëri. Në të majtë shohim ID-në tonë të procesimit. Në kolonën e dytë shohim ID-në tonë virtuale të transaksionit, ndërsa në të tretën shohim lock_type. Çfarë do të thotë kjo? Në thelb, ajo thotë se po bllokon ID-në e transaksionit. Por vini re se në të gjitha rreshtat poshtë shkruan relation. Dhe prandaj keni dy lloje bllokimesh në tabelë. Ka bllokim relation. Po ashtu ka bllokim transactionid, ku ne bllokojmë vetë, kjo është pikërisht ajo që ndodh në rreshtin e parë ose në fund, ku transactionid, ku presim që 699 të përfundojë operacionin e tij.
Po shikoj se çfarë ndodh këtu. Dhe këtu ndodhin dy gjëra njëkohësisht. Ju po shihni bllokimin sipas ID-së së transaksionit në rreshtin e parë, që bllokon veten. Dhe ajo bllokon veten për të bërë që njerëzit të presin.
Nëse shikoni rreshtin e 6-të, është e njëjta shënim si e para. Dhe prandaj transaksioni 699 bllokohet. 700 gjithashtu bllokohet vetë. Dhe pastaj në rreshtin e poshtëm do të shihni se po presim që 699 të përfundojë operacionin e tij.

Dhe në lock_type, tuple shihni numrat.

Mund të shihni se kjo është 0/10. Dhe kjo është numri i faqes, dhe gjithashtu offset i këtij rreshti konkret.

Dhe shihni se bëhet 0/11 kur e përditësojmë.

Por në të vërtetë – kjo është 0/10, sepse po ndodh presioni i këtij operacioni. Ne kemi mundësinë të shohim se ky është rreshti që po pres për të konfirmuar.

Sa herë që e konfirmojmë dhe klikojmë commit, dhe kur përditësimi përfundon, kjo është ajo që marrim përsëri. Transaksioni 700 është bllokimi i vetëm, ai nuk pret askënd tjetër, sepse është konfirmuar. Ai vetëm pret që transaksioni të përfundojë. Sa herë që 699 përfundon, ne nuk presim më. Dhe tani transaksioni 700 thotë se gjithçka është në rregull, të gjitha bllokimet që i duhen, i ka në të gjitha tabelat e lejuara.

Dhe për ta komplikuar akoma më shumë, ne krijojmë një tjetër pamje, e cila këtë herë do të na ofrojë një hierarki. Nuk e pres që ta kuptoni këtë kërkesë. Por kjo do të na japë një pamje më të qartë se çfarë po ndodh.

Kjo është një pamje rekurzive, e cila ka gjithashtu një seksion tjetër. Dhe ajo pastaj kthehet përsëri e gjitha. Le të përdorim këtë.

Çfarë, nëse bëjmë tri përditësime të njëkohshme dhe themi se rreshti tani është i barabartë me tre. Dhe ne do ta ndryshojmë 3 në 4.

Dhe ja, shohim 4. Dhe ID e transaksionit 702.

Dhe pastaj e ndryshoj 4 në 5. Dhe 5 në 6, dhe 6 në 7. Dhe radhit njerëzit që do të presin deri sa kjo një transaksion të përfundojë.

Dhe gjithçka bëhet e qartë. Cili është rreshti i parë? Ky është 702. Ky është ID e transaksionit, që fillimisht caktoi këtë vlerë. Por çfarë kam shkruar në kolonën Granted? Kam shënime f. Këto janë përditësimet e mia, që (5, 6, 7) nuk mund të miratohen, sepse presim që ID e transaksionit 702 të përfundojë. Aty kemi një bllokim të ID e transaksionit. Dhe rezulton 5 bllokime të IDs së transaksionit.
Dhe nëse shikoni 704, në 705, aty nuk ka asgjë të shkruar, sepse ata akoma nuk e dinë se çfarë po ndodh. Ata thjesht shkruajnë, që nuk kanë asnjë ide se çfarë po ndodh. Dhe ata thjesht do të shkojnë në gjumë, sepse presin që dikush të përfundojë dhe t'i zgjojë kur të paraqitet mundësia për të ndryshuar rreshtin.

Kështu duket. E qartë, se ata të gjithë presin rreshtin 12.

Kjo është ajo që pamë këtu. Ja, 0/12.

Dhe kështu, sa herë që transaksioni i parë miratohet, mund të shihni se si funksionon hierarkia. Dhe tani bëhet gjithçka e qartë. Të gjithë bëhen të pastër. Dhe ata në të vërtetë ende janë në pritje.

Këtu është se çfarë po ndodh. 702 po bën komitim. Tani 703 merr këtë bllokim rreshti, dhe pastaj 704 fillon të presë derisa 703 të përfundojë. 705 gjithashtu po pret këtë. Dhe kur të gjithë këto përfundojnë, ata vetë pastrohen. Dëshiroj të theksoj se të gjithë rreshtohen. Kjo është shumë e ngjashme me një situatë ngërçi, kur të gjithë presin makinën e parë. Makinë e parë ndalon, dhe të gjithë rreshtohen në një linjë të gjatë. Pastaj ajo lëviz, pastaj makina tjetër mund të kalojë përpara dhe të marrë bllokimin e saj, etj.

Dhe nëse ky duket si një problem i mjaftueshëm, tani do të flasim për ngërçet. Nuk e di se sa nga ju kanë ardhur në kontakt me to. Kjo është një problem relativisht i zakonshëm në sistemet e bazës së të dhënave. Por ngërçet janë rast kur një seancë pret që një seancë tjetër të kryejë diçka. Ndërkohë, seanca tjetër pret që seanca e parë të kryejë diçka.
Dhe, për shembull, nëse Ivan thotë: «Më jep diçka», dhe unë thëm: «Jo, do të të jap këtë vetëm nëse ti më jep diçka tjetër». Ai thotë: «Jo, nuk do të të jap këtë nëse ti nuk më jep». Kështu krijohet një situatë ngërçi. Jam i sigurt që Ivan nuk do ta bëjë këtë, por ju kuptoni kuptimin, që kemi dy njerëz që duan të marrin diçka dhe ata nuk janë të gatshëm ta japin atë derisa personi tjetër t'ua japë atë që ata duan. Dhe nuk ka zgjidhje.
Dhe, në thelb, baza juaj e të dhënave duhet ta identifikojë këtë. Dhe pastaj është e nevojshme të fshihet ose të mbyllet një nga seancat, sepse në tërësi ato do të mbeten atje përgjithmonë. E shohim këtë në bazat e të dhënave, e shohim në sistemet operative. Dhe në të gjitha vendet ku kemi procese paralele, kjo mund të ndodhë.

Dhe tani do të vendosim dy ngërçet. Ne do të vendosim 50 dhe 80. Në rreshtin e parë do të kryej një përditësim nga 50 në 50. Do të kem numrin e transaksionit 710.

Dhe pastaj do të ndryshoj 80 në 81, dhe 50 në 51.

Kështu do të duket. Dhe prandaj 710 ka bllokimin e rreshtit, ndërsa 711 pret konfirmimin. E pamë këtë kur përditësuam. 710 është pronari i rreshtit tonë. Dhe 711 pret që 710 të përfundojë transaksionin.

Dhe aty madje shkruhet se në cilin rresht ndodhin ngërçet. Dhe këtu fillon të bëhet e çuditshme.

Tani po përditësojmë 80 në 80.

Dhe këtu fillon ngërçi. 710 po pret një përgjigje nga 711, dhe 711 po pret nga 710. Dhe kjo nuk do të përfundojë mirë. Do të presin përgjigje nga njëri-tjetri.

Dhe kjo do të fillojë të vonohet. Dhe nuk e duam këtë.

Dhe në Postgres ka mënyra për të kuptuar kur kjo ndodh. Dhe kur ndodh, ju merrni një mesazh të tillë gabimi. Dhe është e qartë se ky proces po pret një SHARE LOCK nga një proces tjetër, që është bllokuar nga procesi 711. Ai proces priste që t’i jepej një SHARE LOCK në një ID transaksioni të caktuar dhe ishte bllokuar nga një proces i tillë. Prandaj, kjo është një situatë ngërçi.

A ndodhin ngërçet trepalësh? A është e mundur kjo? Po.

Ne i fusim këto numra në tabelë. Ne ndryshojmë 40 në 40, ne bëjmë bllokimin.

Ndryshojmë 60 në 61, 80 në 81.

Dhe pastaj ne ndryshojmë 80, dhe pastaj - bum!

Dhe 714 tani po pret nga 715. 716-i po pret nga 715. Dhe me këtë nuk ka çfarë të bëhet.

Tani nuk ka dy njerëz, por tre. Unë dua diçka nga ti, ky dëshiron diçka nga personi e tretë, dhe personi i tretë dëshiron diçka nga unë. Dhe ne jemi në një pritje trepalëshe, sepse të gjithë presim që tjetri të përfundojë atë që duhet të bëjë.

Dhe Postgres e di në cilin rresht ndodh kjo. Prandaj do t'ju japë një mesazh të tillë, që tregon se keni një problem, ku tre procese po bllokojnë njëri-tjetrin. Dhe këtu nuk ka kufizime. Mundësia është që 20 regjistrime të bllokojnë njëri-tjetrin.

Problemi tjetër është serializable.

Nëse bllokimi special është serializable.

Dhe kthehemi te 719. Ai ka një produkt të plotë normal.

Dhe ju mund të klikoni për të bërë një transaksion nga serializable.

Dhe ju kuptoni se tani keni një lloj tjetër bllokimi SA - që do të thotë serializable.


Prandaj kemi një lloj të ri bllokimi që quhet SARieadLock, i cili është një bllokim seri dhe lejon futjen e serive.

Dhe gjithashtu mund të futni indekse unike.

Në këtë tabelë kemi indekse unike.

Prandaj, nëse unë fus numrin 2 këtu, unë kam 2. Por në krye, unë fus një tjetër 2. Dhe ju mund të shihni që 721 ka bllokim ekskluziv. Por tani 722 po pret që 721 të përfundojë operacionin e tij, sepse ai nuk mund të fusë 2 përderisa nuk e di se çfarë do të ndodhë me 721.

Dhe nëse ne bëjmë subtransaction.

Ja ku jemi me 723.

Nëse ne ruajmë një pikë dhe pastaj e përditësojmë, ne marrim një ID të re transaksioni. Kjo është një tjetër karakteristikë që duhet të dini. Nëse ne e kthejmë atë, ID e transaksionit largohet. 724 largohet. Por tani na del 725.
Çfarë përpiqem të bëj këtu? Po përpiqem t'ju tregoj shembuj të bllokimeve të pazakonta që mund të gjeni: qofshin këto bllokime serializues ose SAVEPOINT – këto janë lloje të ndryshme bllokimesh që do të shfaqen në tabelën e bllokimeve.

Ky është krijimi i bllokimeve eksplicite (të qarta) me pg_advisory_lock.

Dhe ju shihni se tipi i bllokimit këtu është shënuar si advisory. Dhe këtu është shkruar me të kuqe 'advisory'. Dhe ju mund ta bllokoni atë njëkohësisht me pg_advisory_unlock.

Dhe për të përfunduar, do të doja t'ju tregoj diçka tjetër të mahnitshme. Do të krijoj një lloj tjetër. Por do ta lidh tabelën pg_locks me tabelën pg_stat_activity. Pse dua ta bëj këtë? Sepse do të më lejojë të shoh dhe të shikoj të gjitha sesionet aktuale dhe të shoh se çfarë bllokimesh po presin. Dhe kjo është mjaft interesante kur ne bashkojmë tabelën e bllokimeve dhe tabelën e kërkesave.

Dhe këtu krijojmë pg_stat_view.

Dhe ne e përditësojmë një rresht me një. Dhe këtu ne shohim 724. Pastaj ne e përditësojmë rreshtin tonë në tre. Çfarë shihni tani këtu? Këto janë kërkesat, pra, ju shihni të gjithë listën e kërkesave që janë të renditura në kolonën e majtë. Pastaj në anën e djathtë ju mund të shihni bllokimet dhe atë që ato krijojnë. Dhe kjo mund të jetë më e qartë për ju, në mënyrë që të mos keni nevojë të ktheheni çdo herë te çdo sesion dhe të shihni – a duhet të bashkoheni me të apo jo. Kjo e bën për ne.
Një tjetër funksion shumë i dobishëm është pg_blocking_pids. Ndoshta nuk keni dëgjuar ndonjëherë për të. Çfarë bën kjo? Ajo na lejon të themi se për këtë sesion 11740, cilat janë ID-të e proceseve që po pret. Dhe ju mund të shihni se 11740 pret 724. Dhe 724 është në krye. Ndërsa 11306 është ID i procesit tuaj. Në thelb, ky funksion kalon përmes tabelës tuaj të bllokimeve. E di se është pak e komplikuar, por ju po e kuptoni. Në thelb, ky funksion kalon përmes kësaj tabele bllokimesh dhe përpiqet të gjejë se ku është ky ID procesi, duke marrë parasysh ato bllokime që po pret. Dhe gjithashtu përpiqet të llogarisë se cili është ID procesi, i atij procesi që po pret bllokimin. Pra, ju mund ta ekzekutoni këtë funksion pg_blocking_pids.
Dhe kjo është shumë e dobishme. E kemi shtuar këtë vetëm me versionin 9.6, pra, ky funksion është vetëm pesë vjet i vjetër, por është shumë dhe shumë i dobishëm. Dhe kjo vlen edhe për kërkesën e dytë. Ajo tregon pikërisht atë që na nevojitet të shohim.

Kjo është ajo për të cilën doja të flisja me ju. Dhe siç e prisja, ne e përdorëm gjithë kohën tonë, sepse kishte kaq shumë slajde. Dhe slajdet janë të disponueshme për shkarkim. Do doja t'ju falënderoj për praninë tuaj. Jam i sigurt se do t'ju pëlqejë pjesa tjetër e konferencës, faleminderit shumë!
Pyetje:
Për shembull, nëse unë përpiqem të përditësoj rreshta, ndërsa një sesion tjetër përpiqet të fshijë të gjithë tabelën. Siç e kuptoj, aty duhet të jetë diçka si një intent lock. A ekziston diçka e tillë në Postgres?

Kthehemi në fillim. Ndoshta e mbani mend se kur bëni ndonjë gjë, për shembull, kur bëni SELECT, ne lëshojmë AccessShareLock. Dhe kjo parandalon fshirjen e tabelës. Pra, nëse ju, për shembull, doni të përditësoni një rresht në tabelë ose të fshini një rresht, atëherë dikush nuk mund ta fshijë të gjithë tabelën njëkohësisht me këtë, sepse mbani këtë AccessShareLock mbi të gjithë tabelën dhe mbi rreshtin. Dhe sa herë që e përfundoni, ata mund ta fshijë. Por për sa kohë që ju jeni duke bërë diçka atje, ata nuk do të mund ta bëjnë.
Le të bëjmë edhe një herë. Le të kalojmë në shembullin e fshirjes. Dhe ju shihni se si ka një bllokim ekskluziv mbi të gjithë tabelën.
Kjo do të dukej si një bllokim ekskluziv, e drejtë?
Po, po duket kështu. E kuptoj për çfarë po flisni. Po thoni se, nëse unë bëj SELECT, do të kem një ShareExclusive, dhe pastaj e kaloj këtë në një gjendje Row Exclusive, a bëhet kjo një problem? Por për habi, kjo nuk krijon një problem. Kjo duket si një rritje e shkallës së bllokimit, por, në thelb, unë kam një bllokim që parandalon fshirjen. Dhe tani, kur e bëj këtë bllokim më të fuqishëm, ai ende parandalon fshirjen. Prandaj, nuk është se po ngjitem lart. Pra, ai parandalonte edhe kur ishte në një nivel më të ulët, kështu që, kur e rris nivelin e tij, ai ende parandalon fshirjen e tabelës.
E kuptoj se për çfarë po flisni. Nuk ka asnjë rast të rritjes së nivelit të bllokimit, ku përpiqeni të refuzoni një bllokim për të futur një më të fuqishëm. Këtu thjesht rritet ky parandalim, kështu që nuk shkakton asnjë konflikt. Por është një pyetje e mirë. Faleminderit që e ngritët këtë!
Çfarë duhet të bëjmë për të shmangur situatën e deadlock-ut, kur kemi shumë sesi, një numër të madh përdoruesish?
Postgres e evidenton automatikisht situatën e deadlock-ut. Dhe do të eliminojë automatikisht një nga sesionet. Një nga mënyrat që ndihmon të shmanget situata e bllokimeve të vdekura është të bllokoni njerëzit në të njëjtën renditje. Pra, kur shikoni aplikacionin tuaj, shpesh arsyeja e deadlocks... Le të imagjinojmë se dua të bllokoj dy gjëra të ndryshme. Një aplikacion bllokon tabelën 1, dhe një aplikacion tjetër bllokon tabelën 2, e më pas tabelën 1. Dhe mënyra më e thjeshtë për të shmangur deadlocks është të shikoni aplikacionin tuaj dhe të siguroheni që bllokimi ndodh në të njëjtën renditje në të gjitha aplikacionet. Kjo, në përgjithësi, eliminon 80% të problemeve, sepse njerëz të ndryshëm i shkruajnë këto aplikacione. Dhe nëse i bllokoni në të njëjtën renditje, atëherë nuk përballeni me situatën e deadlock-ut.
Faleminderit shumë për fjalimin tuaj! Flisnit për vacuum full dhe, nëse e kuptoj mirë, vacuum full deformon rendin e regjistrimeve në ruajtjen e veçantë, prandaj ruan regjistrimet aktuale të pandryshuara. Pse vacuum full merr akses bllokues ekskluziv dhe pse ai konflikon me operacionet e shkrimit?
Është një pyetje e mirë. Arsyeja është se vacuum full merr tabelën. Dhe në thelb krijojmë një version të ri të tabelës. Dhe tabela do të jetë e re. Pra, do të kemi një version krejtësisht të ri të tabelës. Problemi është se, kur e bëjmë këtë, nuk duam që njerëzit ta lexojnë këtë, sepse na duhet që ata të shohin tabelën e re. Dhe prandaj kjo lidhet me pyetjen e mëparshme. Nëse do të mund të lexohej njëkohësisht, atëherë nuk do të mund të lëviznim dhe të drejtonim njerëzit në tabelën e re. Do të duhej të prisnim që secili të përfundonte të lexonte këtë tabelë, dhe prandaj, në thelb, kjo është një situatë bllokimi ekskluziv.
Thjesht po themi se bllokojmë qysh në fillim, sepse e dimë se në fund do të na nevojitet një bllokim ekskluziv për të lëvizur të gjithë në një kopje të re. Pra, potencialisht mund ta zgjidhim këtë. Dhe ne e bëjmë këtë me indeksim paralel. Por kjo është shumë më e komplikuar për t'u realizuar. Dhe kjo lidhet shumë me pyetjen tuaj të mëparshme mbi bllokimin ekskluziv.
A është e mundur të shtoni një kohë skadimi për bllokimin në Postgres? Në Oracle mund të shkruaj, për shembull, 'zgjedh për të përditësuar' dhe të pres 50 sekonda për përditësimin. Kjo ishte e mirë për aplikacionin. Por në Postgres unë ose duhet ta bëj menjëherë dhe të mos pres fare, ose të pres deri në një moment të caktuar.
Po, mund të zgjidhni një kohë skadimi për bllokimet tuaja. Gjithashtu, mund të jepni urdhërin no way, i cili do të... nëse nuk mund të merrni menjëherë bllokimin. Pra, ose kohë skadimi bllokimi, ose diçka tjetër që do t'ju lejojë ta bëni këtë. Kjo nuk bëhet në nivelin sintaksor. Kjo bëhet si një variabël në server. Disa herë kjo nuk mund të përdoret.
Mund të hapni sliden 75?
Po.

Dhe pyetja ime është kjo. Pse të dy proceset e përditësimit presin 703?
Dhe kjo është një pyetje e shkëlqyer. Nuk e kuptoj, përsa i përket, pse Postgres e bën këtë. Por kur 703 u krijua, ai priste 702. Dhe kur 704 dhe 705 shfaqen, duket se ata nuk e dinë se çfarë po presin, sepse atje nuk ka ende asgjë. Dhe Postgres e bën këtë: kur nuk mund të merrni bllokimin, ai shkruan 'Cila është pika të vazhdoni me ju?', sepse ju tashmë po prisni dikë. Prandaj, thjesht le të mbesë në pritje, ai nuk e përditëson fare. Por çfarë ndodhi këtu? Sapo 702 përfundoi procesin dhe 703 mori bllokimin e tij, sistemi u kthye mbrapa. Dhe tha tani kemi dy njerëz që janë në pritje. Dhe tani le t'i përditësojmë ata së bashku.
Nuk di, pse Postgres e bënë kështu. Por ka një problem që quhet f…. Më duket se nuk është një term në rusisht. Kjo është kur të gjithë presin një bllokim, edhe pse ka 20 instanca që presin bllokimin. Dhe papritmas ata të gjithë zgjojnë njëherësh. Dhe të gjithë fillojnë të përpiqen të reagojnë. Por sistemi e bën që të gjithë të presin 703. Sepse ata të gjithë presin, dhe ne do t’i rendisim menjëherë në radhë. Dhe nëse ndonjë kërkesë e re shfaqet, e formuar pas kësaj, për shembull, 707, atje sërish do të ketë boshllëk.
Më duket se kjo bëhet për të thënë se në këtë fazë 702 pret 703, dhe të gjithë ata që do të vijnë pas kësaj, nuk do të kenë asnjë shënim në këtë fushë. Por sa herë që ai që pret i largohet, të gjithë ata që prisnin në atë moment deri në përditësim, marrin të njëjtin shenjë. Prandaj, më duket se kjo është bërë për të mundësuar që të trajtojmë në rend, që ata të jenë në rend të duhur.
Unë gjithmonë e kam parë këtë si një fenomen mjaft të çuditshëm. Sepse këtu, për shembull, ne nuk i rendisim fare. Por, më duket se çdo herë që ne japim një bllokim të ri, ne shohim të gjithë ata që janë në procesin e pritjes. Atëherë i rendisim të gjithë në radhë. Dhe çdo i ri që vjen, bie në radhë vetëm atëherë kur personi tjetër përfundon procesin. Një pyetje shumë e mirë. Faleminderit shumë për pyetjen!
Më duket se është shumë më logjike kur 705 pret 704.
Por problemi këtu është si vijon. Teknikisht ju mund të zgjoni atë, ose këtë. Dhe prandaj ne do të zgjojmë atë ose këtë. Por çfarë ndodh në funksionimin e sistemit? Ju shihni se si 703 në krye bllokoi ID-në e tij të transaksionit. Kështu funksionon Postgres. Dhe 703 bllokohet nga ID-ja e tij e transaksionit, prandaj, nëse dikush dëshiron të presë, do të presë 703. Dhe, në thelb, 703 përfundon. Dhe vetëm pas përfundimit të tij, ndonjë nga proceset zgjohet. Dhe ne nuk e dimë se cili proces do të jetë. Pastaj ne e trajtojmë gradualisht të gjithin. Por nuk është e qartë se cili proces zgjohet i pari, sepse mund të jetë çfarëdo nga këta procese. Në thelb, kishim një planifikues që tha se tani mund të zgjojmë çfarëdo nga këta procese. Thjesht zgjedhim një rastësisht. Prandaj të dy duhet të merren parasysh, sepse ne mund të zgjojmë çfarëdo nga ata.
Dhe problemi është që kemi CP-përjetësi. Prandaj është shumë e mundur që të zgjojmë më vonë. Dhe nëse, për shembull, ne do të zgjojmë më vonë, atëherë do të presim atë që sapo mori bllokimin, prandaj nuk e përcaktojmë se kush do të zgjohet i pari. Ne thjesht krijojmë një situatë të tillë, dhe sistemi do t’i zgjojë ata në mënyrë të rastësishme.
Ka . Shikoni, ato janë gjithashtu interesante dhe të dobishme. Tema, sigurisht, është shumë e komplikuar. Faleminderit shumë, Bruce!
Burimi: habr.com
