Pra ndaj, ne shqyrtuam çështjet e lidhura me , dhe u bĂ«mĂ« njĂ« shkĂ«putje rreth . Dhe mĂ« nĂ« fund arritĂ«m nĂ« mĂ« tĂ« mirĂ«n â versionet e rreshtave.
Header-i
Siç e kemi thĂ«nĂ«, çdo rresht mund tĂ« jetĂ« njĂ«kohĂ«sisht nĂ« bazĂ«n e tĂ« dhĂ«nave nĂ« disa versione. NjĂ« version duhet ndryshe tĂ« ndahen nga tjetra. Me kĂ«tĂ« qĂ«llim, çdo version ka dy shenjat, qĂ« pĂ«rcaktojnĂ« "kohĂ«n" e veprimit tĂ« kĂ«saj versioni (xmin dhe xmax). Citatet â sepse pĂ«rdoret jo koha si e tillĂ«, por njĂ« numĂ«rator i veçantĂ« nĂ« rritje. Dhe ky numĂ«rues Ă«shtĂ« numri i transaksionit.
(Si zakonisht, nĂ« tĂ« vĂ«rtetĂ« gjithçka Ă«shtĂ« mĂ« e komplikuar: numri i transaksioneve nuk mund tĂ« rritet gjithmonĂ« pĂ«r shkak tĂ« kapacitetit tĂ« kufizuar tĂ« numĂ«ruesit. Por kĂ«to detaje do tâi shqyrtojmĂ« nĂ« detaje kur tĂ« arrijmĂ« nĂ« ngrirjen.)
Kur krijohet një rresht, vlera xmin vendoset në numrin e transaksionit që e ekzekuton komandën INSERT, dhe xmax nuk plotësohet.
Kur një rresht fshihet, vlera xmax e versionit aktual shënohet me numrin e transaksionit që e ekzekuton DELETE.
Kur një rresht ndryshohet me komandën UPDATE, në të vërtetë realizohen dy operacione: DELETE dhe INSERT. Në versionin aktual të rreshtit vendoset xmax, e barabartë me numrin e transaksionit që e ekzekuton UPDATE. Më pas krijohet një version i ri i të njëjtit rresht; vlera xmin e tij përputhet me vlerën xmax të versionit të mëparshëm.
Fushat xmin dhe xmax përfshihen në titullin e versionit të rreshtit. Përveç këtyre fusha, titulli përmban dhe të tjera, për shembull:
- infomask â njĂ« rresht bitĂ«sh, qĂ« pĂ«rcakton pronat e kĂ«tij versioni. JanĂ« shumĂ« nga to; disa nga ato kryesore do tâi shqyrtojmĂ« gradualisht.
- ctid â njĂ« referencĂ« pĂ«r versionin tjetĂ«r, mĂ« tĂ« ri, tĂ« tĂ« njĂ«jtit rresht. NĂ« versionin mĂ« tĂ« ri, aktual, tĂ« rreshtit çtid referon nĂ« kĂ«tĂ« version. Numri ka formĂ«n (x,y), ku x Ă«shtĂ« numri i faqes, y Ă«shtĂ« numri rendor i treguesit nĂ« serinĂ«.
- njĂ« hartĂ« bitĂ«sh pĂ«r vlerat e pasiguruara â tregon ato kolona tĂ« kĂ«tij versioni qĂ« pĂ«rmbajnĂ« njĂ« vlerĂ« tĂ« pasigurtĂ« (NULL). NULL nuk Ă«shtĂ« njĂ« nga vlerat e zakonshme tĂ« tipeve tĂ« dhĂ«nash, prandaj treguesi duhet ruajtur veçmas.
Si rezultat, titulli del mjaft i madh â minimalisht 23 byte pĂ«r çdo version tĂ« rreshtit, dhe zakonisht mĂ« shumĂ« pĂ«r shkak tĂ« hartĂ«s sĂ« NULL-Ă«ve. NĂ«se tabela Ă«shtĂ« "e ngushtĂ«" (dmth. pĂ«rmban pak kolona), shpenzimet mund tĂ« zĂ«nĂ« mĂ« shumĂ« se informacioni i dobishĂ«m.
Inserting
Le të shqyrtojmë më në detaje se si kryhen operacionet me vargje në nivel të ulët, duke filluar nga inserimi.
Për eksperimentet do të krijojmë një tabelë të re me dy kolona dhe një indeks mbi një prej tyre:
=> KRIJO TABELĂ t(
id serial,
s tekst
);
=> KRIJO INDENSĂ NĂ t(s);
Do të vendosim një varg, duke filluar më parë transaksionin.
=> FILLON;
=> SHTO NĂ t(s) VLERAT ('FOO');
Ja numri i transaksionit tonë aktual:
=> ZGJIDH txid_current();
txid_current
--------------
3664
(1 rresht)
Le të shikojmë përmbajtjen e faqes. Funksioni heap_page_items i zgjerimit pageinspect lejon të merrni informacion mbi referencat dhe versionet e vargjeve:
=> ZGJIDH * NGA heap_page_items(merr_raw_page('t',0)) gx
-[ REGJISTRI 1 ]-------------------
lp | 1
lp_ofs | 8160
lp_flags | 1
lp_gjatësia | 32
t_xmin | 3664
t_xmax | 0
t_field3 | 0
t_ctid | (0,1)
t_infomask2 | 2
t_infomask | 2050
t_hoff | 24
t_bits |
t_oid |
t_data | x0100000009464f4f
VĂ«rejmĂ« se me fjalĂ«n heap (stiva) nĂ« PostgreSQL pĂ«rfaqĂ«sohen tabelat. Kjo Ă«shtĂ« njĂ« tjetĂ«r pĂ«rdorim i çuditshĂ«m i termit â stiva Ă«shtĂ« njĂ« strukturĂ« e njohur tĂ« dhĂ«nash Funksioni tregon tĂ« dhĂ«nat 'ashtu si janĂ«', nĂ« njĂ« format tĂ« komplikuar pĂ«r t'u kuptuar. PĂ«r tĂ« kuptuar, do tĂ« lĂ«mĂ« vetĂ«m njĂ« pjesĂ« tĂ« informacionit dhe do ta shpjegojmĂ«:
=> ZGJIDH '(0,'||lp||')' SI ctid, RAST lp_flags KUR 0 ATYHER 'e pashfrytëzuar' KUR 1 ATYHER 'normal' KUR 2 ATYHER 'drejtohet në '||lp_ofs KUR 3 ATYHER 'i vdekur' FUND AS state, t_xmin si xmin, t_xmax si xmax, (t_infomask & 256) > 0 SI xmin_commited, (t_infomask & 512) > 0 SI xmin_aborted, (t_infomask & 1024) > 0 SI xmax_commited, (t_infomask & 2048) > 0 SI xmax_aborted, t_ctid NGA heap_page_items(merr_raw_page('t',0)) gx
-[ REGJISTRI 1 ]-+-------
ctid | (0,1)
state | normal
xmin | 3664
xmax | 0
xmin_commited | f
xmin_aborted | f
xmax_commited | f
xmax_aborted | t
t_ctid | (0,1)
Ja çfarë kemi bërë:
Shtuam një zero në numrin e referencës për ta sjellë në një format të njëjtë me t_ctid: (numri i faqes, numri i referencës).
- Shpjeguam gjendjen e referencĂ«s lp_flags. KĂ«tu Ă«shtĂ« 'normal' â kjo do tĂ« thotĂ« se referenca vĂ«rtet i referohet versionit tĂ« vargut. Vlerat e tjera do t'i shqyrtojmĂ« mĂ« vonĂ«.
- Nga të gjitha informacionet bit na u ndanë për momentin vetëm dy çiftet. Bitet xmin_committed dhe xmin_aborted tregojnë nëse transaksioni me numrin xmin është përfunduar (anuluar). Dy bita të ngjashëm kanë të bëjnë me transaksionin me numrin xmax.
- Nga të gjitha bitet informative, deri tani janë identifikuar vetëm dy çifte. Bitet xmin_committed dhe xmin_aborted tregojnë nëse transaksioni me numrin xmin është konfirmuar (apo anuluar). Dy bita të ngjashëm i takojnë transaksionit me numrin xmax.
ĂfarĂ« po shohim? Kur futet njĂ« rresht nĂ« njĂ« faqe tabelĂ« do tĂ« shfaqet njĂ« tregues me numrin 1, i referuar versionit tĂ« parĂ« dhe tĂ« vetĂ«m tĂ« rreshtit.
Në versionin e rreshtit, fusha xmin mbushet me numrin e transaksionit aktual. Transaksioni ende është aktiv, ndaj të dy bitët xmin_committed dhe xmin_aborted nuk janë vendosur.
Fusha ctid e versionit të rreshtit i referohet të njëjtit rresht. Kjo do të thotë se nuk ekziston një version më i ri.
Fusha xmax është mbushur me numrin fictiv 0, pasi ky version i rreshtit nuk është fshirë dhe është aktual. Transaksionet nuk do të marrin parasysh këtë numër, sepse biti xmax_aborted është vendosur.
Të bëjmë një hap tjetër për të përmirësuar lexueshmërinë, duke shkruar bitët informues për numrat e transaksioneve. Dhe do të krijojmë një funksion, sepse na nevojitet ky kërkesë edhe një herë:
=> KRIJO FUNKSION heap_page(relname text, pageno integer)
KTHEN TABELĂ(ctid tid, gjendje text, xmin text, xmax text, t_ctid tid)
AS $$
SELECT (pageno,lp)::text::tid AS ctid,
RAST si lp_flags
KUR 0 ATĂHERĂ 'tĂ« papĂ«rdorur'
KUR 1 ATĂHERĂ 'normal'
KUR 2 ATĂHERĂ 'redirect to '||lp_off
KUR 3 ATĂHERĂ 'i vdekur'
FUND AS gjendje,
t_xmin || RAST
KUR (t_infomask & 256) > 0 ATĂHERĂ ' (c)'
KUR (t_infomask & 512) > 0 ATĂHERĂ ' (a)'
NDIHMOJ ' '
FUND AS xmin,
t_xmax || RAST
KUR (t_infomask & 1024) > 0 ATĂHERĂ ' (c)'
KUR (t_infomask & 2048) > 0 ATĂHERĂ ' (a)'
NDIHMOJ ' '
FUND AS xmax,
t_ctid
FROM heap_page_items(get_raw_page(relname,pageno))
ORDER BY lp;
$$ GJUHA SQL;
Në këtë formë është shumë më e qartë se çfarë po ndodh në titullin e versionit të rreshtit:
=> SELEKTO * NGA heap_page('t',0);
ctid | gjendje | xmin | xmax | t_ctid
-------+--------+------+-------+--------
(0,1) | normal | 3664 | 0 (a) | (0,1)
(1 rresht)
Informacione të ngjashme, por ndjeshëm më pak të detajuara, mund të merret edhe nga tabela e vet, duke përdorur pseudostolbow xmin dhe xmax:
=> SELEKTO xmin, xmax, * NGA t;
xmin | xmax | id | s
------+------+----+-----
3664 | 0 | 1 | FOO
(1 rresht)
Regjistrimi
Pas përfundimit të suksesshëm të transaksionit, është e nevojshme të mbahen mend statuset e tij - të shënohet se është regjistruar. Për këtë përdoret një strukturë e quajtur XACT (dhe deri në versionin 10 ishte quajtur CLOG (regjistrimi i komitit) dhe ky emër mund të shfaqet ende në disa vende).
XACT nuk është një tabelë e katalogut sistemor; këto janë skedarë në katalogun PGDATA/pg_xact. Në to për çdo transaksion janë rezervuar dy bita: committed dhe aborted - pikërisht si në titullin e versionit të rreshtit. Kjo informacion është e ndarë në disa skedarë thjesht për arsye komoditeti, ne do të rikthehemi në këtë çështje kur të shqyrtojmë ngrihjen. Dhe puna me këta skedarë bëhet faqe pas faqe si me të gjitha të tjera.
Pra të regjistruar një transaksion në XACT, biti i angazhuar për këtë transaksion vendoset. Dhe kjo është gjithçka që ndodh gjatë regjistrimit (në të vërtetë, ne ende nuk po flasim për ditarin e para-regjistrimit).
Kur ndonjë transaksion tjetër i qaset faqes së tabelës që sapo panë, do t'i duhet të përgjigjet disa pyetjeve.
- A është përfunduar transaksioni xmin? Nëse jo, versioni i krijuar i rreshtit nuk duhet të jetë i dukshëm.
Kjo verifikim kryhet duke parĂ« njĂ« strukturĂ« tjetĂ«r qĂ« ndodhet nĂ« memorien e pĂ«rbashkĂ«t tĂ« instancĂ«s dhe quhet ProcArray. Atje ndodhet lista e tĂ« gjithĂ« proceseve aktivĂ«, dhe pĂ«r çdo njĂ« prej tyre Ă«shtĂ« treguar numri i transaksionit tĂ« saj aktual (aktiv). - NĂ«se Ă«shtĂ« pĂ«rfunduar, atĂ«herĂ« si â me angazhim ose tĂ«rheqje? NĂ«se me tĂ«rheqje, atĂ«herĂ« versioni i rreshtit gjithashtu nuk duhet tĂ« jetĂ« i dukshĂ«m.
Pikërisht për këtë është nevojshme XACT. Por, ndonëse faqet më të fundit të XACT ruhen në buferat në memorien RAM, përsëri kontrollimi i XACT çdo herë është i kushtueshëm. Prandaj, statusi i zbuluar një herë i transaksionit regjistrohet në bitet xmin_committed dhe xmin_aborted të versionit të rreshtit. Nëse një prej këtyre biteve është vendosur, atëherë gjendja e transaksionit xmin konsiderohet e njohur dhe transaksioni tjetër nuk do të duhet të dëgjojë XACT.
Pse këto bite nuk vendosen nga vetë transaksioni që kryen mbushjen? Kur ndodh mbushja, transaksioni ende nuk e di nëse do të përfundojë me sukses. Dhe në momentin e angazhimit, nuk është më e qartë se cilat rreshta në cilat faqe janë ndryshuar. Mund të ketë shumë të tilla, dhe mbajtja e tyre nuk është e dobishme. Për më tepër, disa faqe mund të jenë dëbuar nga buferi në disk; rihapja e tyre për të ndryshuar bitet do të thoshte të ngadalësonte ndjeshëm angazhimin.
Anasjelltas e kursimit Ă«shtĂ« se, pas ndryshimeve, çdo transaksion (edhe ai qĂ« kryen njĂ« lexim tĂ« thjeshtĂ« â SELECT) mund tĂ« fillojĂ« tĂ« ndryshojĂ« faqet e tĂ« dhĂ«nave nĂ« bufer.
Pra, le të regjistrojmë ndryshimin.
=> ANGAZHO;
Në faqe nuk ka ndryshuar asgjë (por ne e dimë se statusi i transaksionit është regjistruar tashmë në XACT):
=> SELEKTO * NGA heap_page('t',0);
ctid | gjendje | xmin | xmax | t_ctid
-------+--------+------+-------+--------
(0,1) | normal | 3664 | 0 (a) | (0,1)
(1 rresht)
Tani, transaksioni që u qas për herë të parë në faqe, do të duhet të përcaktojë statusin e transaksionit xmin dhe ta regjistrojë atë në bitet informative:
=> ZGJIDH * NGA t;
id | s
----+-----
1 | FOO
(1 rresht)
=> SELEKTO * NGA heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+----------+-------+--------
(0,1) | normal | 3664 (a) | 0 (t) | (0,1)
(1 rresht)
Ăinstalim
Kur eliminimin e një rreshti në fushën xmax, numri i transaksionit që po eliminon shënohet në versionin aktual, ndërsa bita xmax_aborted resetohet.
Vërejmë se vlera e vendosur xmax, e cila i përket transaksionit aktiv, vepron si bllokim për rreshtin. Nëse një transaksion tjetër përpiqet të përditësojë ose të fshijë këtë rresht, ai do të detyrohet të presë për përfundimin e transaksionit xmax. Më shumë rreth bllokimeve do të flasim më vonë. Deri atëherë, vërejmë vetëm se numri i bllokimeve të rreshtave nuk ka asnjë limit. Ato nuk zënë hapësirë në memorien operative dhe performanca e sistemit nuk vuan nga numri i tyre. Sidoqoftë, transaksionet
Do të fshijmë rreshtin.
=> FILLIM;
=> FSHIJ NGA t;
=> SELEKTO txid_current();
txid_current
--------------
3665
(1 rresht)
Vëmë re se numri i transaksionit është regjistruar në fushën xmax, por bitat informativë nuk janë vendosur:
=> SELEKTO * NGA heap_page('t',0);
ctid | gjendja | xmin | xmax | t_ctid
-------+--------+----------+------+--------
(0,1) | normal | 3664 (c) | 3665 | (0,1)
(1 rresht)
Anulimi
Anulimi i ndryshimeve funksionon në mënyrë të ngjashme me angazhimin, përveç se në XACT për transaksionin vendoset bita aborted. Anulimi ekzekutohet me të njëjtin shpejtësi si angazhimi. Ndërsa komanda quhet ROLLBACK, nuk ndodh një rikthim i ndryshimeve: gjithçka që transaksioni arriti të ndryshojë në faqet e të dhënave mbetet pa ndryshim.
=> ROLLBACK;
=> SELEKTO * NGA heap_page('t',0);
ctid | gjendja | xmin | xmax | t_ctid
-------+--------+----------+------+--------
(0,1) | normal | 3664 (c) | 3665 | (0,1)
(1 rresht)
Kur i qasemi faqes do të kontrollohet statusi dhe në versionin e rreshtit do të vendoset bita e sugjerimit xmax_aborted. Numri xmax mbetet në faqe, por askush nuk do ta shikojë atë më.
=> ZGJIDH * NGA t;
id | s
----+-----
1 | FOO
(1 rresht)
=> SELEKTO * NGA heap_page('t',0);
ctid | gjendja | xmin | xmax | t_ctid
-------+--------+----------+----------+--------
(0,1) | normal | 3664 (c) | 3665 (a) | (0,1)
(1 rresht)
Përditësim
Përditësimi funksionon ashtu siç do të ishte bërë fillimisht një fshirje e versionit aktual të rreshtit dhe pastaj një futur i ri.
=> FILLIM;
=> PĂRDITĂSO t SET s = 'BAR';
=> SELEKTO txid_current();
txid_current
--------------
3666
(1 rresht)
Kërkesa nxjerr një rresht (versionin e ri):
=> ZGJIDH * NGA t;
id | s
----+-----
1 | BAR
(1 rresht)
Por në faqe ne shohim të dy versionet:
=> SELEKTO * NGA heap_page('t',0);
ctid | gjendja | xmin | xmax | t_ctid
-------+--------+----------+-------+--------
(0,1) | normal | 3664 (c) | 3666 | (0,2)
(0,2) | normal | 3666 | 0 (a) | (0,2)
(2 rreshta)
Versioni i fshirë është shënuar me numrin e transaksionit aktual në fushën xmax. Për më tepër, kjo vlerë është regjistruar mbi të vjetrën, pasi transaksioni i mëparshëm është anuluar. Ndërsa bita xmax_aborted është resetuar, pasi statusi i transaksionit aktual ende nuk është i njohur.
Versioni i parë i rreshtit tani i referohet të dytit (fusha t_ctid), si versionit më të ri.
Në faqen e indekseve shfaqet një tregues i dytë dhe një rresht i dytë që referon në versionin e dytë në faqen tabelare.
Ashtu si në rastin e fshirjes, vlera xmax në versionin e parë të rreshtit shërben si tregues se rreshti është i bllokuar.
Dhe tani do ta mbyllim transaksionin.
=> ANGAZHO;
Indeksi
Derisa tani kemi folur vetĂ«m pĂ«r faqet tabelare. ĂfarĂ« ndodh brenda indekseve?
Informacioni në faqet e indekseve varet shumë nga lloji specifik i indeksit. Edhe një lloj indeksi mund të ketë lloje të ndryshme faqesh. Për shembull, një B-tree ka një faqe me meta të dhëna dhe faqe 'të zakonshme'.
Megjithatë, zakonisht në një faqe ka një array treguesish për rreshtat dhe rreshtat vetë (ashtu si në faqen tabelare). Përveç kësaj, në fund të faqes lëshohet hapësirë për të dhëna speciale.
Rreshtat në indekse gjithashtu mund të kenë struktura shumë të ndryshme në varësi të llojit të indeksit. Për shembull, për një B-tree, rreshtat që i përkasin faqeve gjethe përmbajnë vlerën e çelësit të indeksimit dhe një referencë (ctid) në rreshtin përkatës të tabelës. Në përgjithësi, indeksi mund të jetë ndërtuar në një mënyrë të krejtësisht tjetër.
Momentin mĂ« tĂ« rĂ«ndĂ«sishĂ«m Ă«shtĂ« se nĂ« indekse tĂ« çdo lloji nuk ka versione tĂ« rreshtave. Ose mund tĂ« mendohet se çdo rresht pĂ«rfaqĂ«sohet nga pikĂ«risht njĂ« version. Me fjalĂ« tĂ« tjera, nĂ« krye tĂ« rreshtit tĂ« indeksit nuk ka fusha xmin dhe xmax. Mund tĂ« mendohet se referencat nga indeksi shkojnĂ« nĂ« tĂ« gjitha versionet tabelare tĂ« rreshtave â kĂ«shtu qĂ« pĂ«r tĂ« kuptuar se cilin nga versionet do ta shohĂ« transaksioni, mund tĂ« shikohet vetĂ«m nĂ« tabelĂ«. (Si zakonisht, kjo nuk Ă«shtĂ« e gjithĂ« e vĂ«rteta. NĂ« disa raste, harta e dukshmĂ«risĂ« lejon optimizimin e procesit, por do ta shqyrtojmĂ« kĂ«tĂ« mĂ« vonĂ«.)
Në të njëjtën kohë, në faqen e indeksit zbulojmë tregues për të dy versionet, si për atë aktual ashtu edhe për atë të vjetëruar:
=> SELECT itemoffset, ctid FROM bt_page_items('t_s_idx',1);
itemoffset | ctid
------------+-------
1 | (0,2)
2 | (0,1)
(2 rreshta)
Transaksionet virtuale
Në praktikë, PostgreSQL përdor një optimizim që lejon 'kursimin' e numrave të transaksioneve.
Nëse transaksioni lexon vetëm të dhënat, atëherë ai nuk ndikon në dukshmërinë e versioneve të rreshtave. Prandaj, fillimisht procesi shërbues i jep transaksioneve një numër virtual (virtual xid). Numri përbëhet nga identifikuesi i procesit dhe një numër rresht.
Kjo lëshim i numrit nuk kërkon sinkronizim midis të gjithë proceseve dhe, prandaj, kryhet shumë shpejt. Arsyen tjetër për përdorimin e numrave virtualë do ta shqyrtojmë kur të flasim për ngrirjen.
Numrat virtualë nuk merret parasysh në skenat e të dhënave.
Në momente të ndryshme në sistem, mund të ekzistojnë transaksione virtuale me numra që kanë qenë tashmë në përdorim, dhe kjo është e pranueshme. Por një numër i tillë nuk mund të regjistrohet në faqet e të dhënave, sepse kur të bëni referencë të ardhshme në faqen, ai mund të humbasë çdo kuptim.
=> FILLIM;
=> ZGJEDH txid_current_if_assigned();
txid_current_if_assigned
--------------------------
(1 rresht)
Nëse transaksioni fillon të ndryshojë të dhënat, atij i jepet një numër transaksioni i vërtetë, unik.
=> UPDATE accounts SET amount = amount - 1.00;
=> ZGJEDH txid_current_if_assigned();
txid_current_if_assigned
--------------------------
3667
(1 rresht)
=> ANGAZHO;
Transaksionet e nëndheshme
Pikat e ruajtjes
Në SQL janë të përcaktuara pikat e ruajtjes (savepoint), të cilat lejojnë të anulojnë një pjesë të operacioneve të transaksionit, pa e ndërprerë atë plotësisht. Por kjo nuk përputhet me skemën e mësipërme, sepse statusi i transaksionit është i vetëm për të gjitha ndryshimet e tij, dhe asnjë të dhënë nuk rikthehet fizikisht.
Për të realizuar një funksionalitet të tillë, transaksioni me pikën e ruajtjes ndahet në disa transaksione të nëndheshme (subtransaction), të cilat mund të menaxhohen veçmas.
Transaksionet e nëndheshme kanë numrin e tyre të vet (më të madh se numri i transaksionit kryesor). Statusi i transaksioneve të nëndheshme regjistrohet në mënyrë të zakonshme në XACT, megjithatë statusi përfundimtar varet nga statusi i transaksionit kryesor: nëse ai anullohet, atëherë anullohen gjithashtu të gjitha transaksionet e nëndheshme.
Informacioni rreth nëndeshmërisë së transaksioneve ruhet në skedarët në katalogun PGDATA/pg_subtrans. Qasja në skedarët ndodh përmes tamponëve në memorjen e përbashkët të instancës, të organizuara ashtu si tamponët XACT.
Mos e ngatërroni transaksionet e nëndheshme me transaksionet autonome. Transaksionet autonome nuk varen nga njëra-tjetra, ndërsa transaksionet e nëndheshme varen. Nuk ka transaksionesh autonome në PostgreSQL të zakonshëm, dhe ndoshta është për mirë: ato janë të nevojshme shumë rrallë, dhe pranimi i tyre në sisteme të tjera DB provokojnë abuzime, nga të cilat pastaj të gjithë vuajnë.
Të pastrojmë tabelën, të fillojmë transaksionin dhe të shtojmë një rresht:
=> TRUNCATE TABLE t;
=> BEGIN;
=> INSERT INTO t(s) VALUES ('FOO');
=> SELECT txid_current();
txid_current
--------------
3669
(1 row)
=> SELEKTO xmin, xmax, * NGA t;
xmin | xmax | id | s
------+------+----+-----
3669 | 0 | 2 | FOO
(1 row)
=> SELEKTO * NGA heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+------+-------+--------
(0,1) | normal | 3669 | 0 (a) | (0,1)
(1 row)
Tani për një pikë ruajtjeje dhe do të shtojmë një rresht tjetër.
=> SAVEPOINT sp;
=> INSERT INTO t(s) VALUES ('XYZ');
=> SELECT txid_current();
txid_current
--------------
3669
(1 row)
Vini re se funksioni txid_current() jep numrin e transaksionit kryesor, jo atë të brendshëm.
=> SELEKTO xmin, xmax, * NGA t;
xmin | xmax | id | s
------+------+----+-----
3669 | 0 | 2 | FOO
3670 | 0 | 3 | XYZ
(2 rows)
=> SELEKTO * NGA heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+------+-------+--------
(0,1) | normal | 3669 | 0 (a) | (0,1)
(0,2) | normal | 3670 | 0 (a) | (0,2)
(2 rows)
Do tâi kthehemi pikĂ«s sĂ« ruajtjes dhe do tĂ« shtojmĂ« rreshtin e tretĂ«.
=> ROLLBACK TO sp;
=> INSERT INTO t(s) VALUES ('BAR');
=> SELECT xmin, xmax, * FROM t;
xmin | xmax | id | s
------+------+----+-----
3669 | 0 | 2 | FOO
3671 | 0 | 4 | BAR
(2 rows)
=> SELEKTO * NGA heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+----------+-------+--------
(0,1) | normal | 3669 | 0 (a) | (0,1)
(0,2) | normal | 3670 (a) | 0 (a) | (0,2)
(0,3) | normal | 3671 | 0 (a) | (0,3)
(3 rows)
Në faqe vazhdojmë të shohim rreshtin e shtuar nga transaksioni i anuluar.
Tërheqim ndryshimet.
=> COMMIT;
=> SELECT xmin, xmax, * FROM t;
xmin | xmax | id | s
------+------+----+-----
3669 | 0 | 2 | FOO
3671 | 0 | 4 | BAR
(2 rows)
=> SELEKTO * NGA heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+----------+-------+--------
(0,1) | normal | 3669 (c) | 0 (a) | (0,1)
(0,2) | normal | 3670 (a) | 0 (a) | (0,2)
(0,3) | normal | 3671 (c) | 0 (a) | (0,3)
(3 rows)
Tani është qartë se çdo transaksion i brendshëm ka statusin e vet.
Vini re se transaksionet e brendshme nuk mund të përdoren në SQL në mënyrë të qartë, që do të thotë se nuk mund të filloni një transaksion të ri pa përfunduar atë aktual. Ky mekanizëm aktivizohet në mënyrë të heshtur kur përdoren pikë ruajtjeje, siç ndodhi me përpunimin e përjashtimeve PL/pgSQL dhe në disa raste të tjera më ekzotike.
=> BEGIN;
FILLON
=> BEGIN;
WARNING: there is already a transaction in progress
BEGIN
=> ANGAZHO;
KREJT
=> ANGAZHO;
WARNING: there is no transaction in progress
COMMIT
Gabimet dhe atomizmi i operacioneve
ĂfarĂ« do tĂ« ndodhĂ« nĂ«se ndodh njĂ« gabim gjatĂ« ekzekutimit tĂ« njĂ« operacioni? PĂ«r shembull, kĂ«shtu:
=> BEGIN;
=> SELECT * FROM t;
id | s
----+-----
2 | FOO
4 | BAR
(2 rows)
=> UPDATE t SET s = repeat('X', 1/(id-4));
ERROR: division by zero
Ka ndodhur një gabim. Tani transaksioni konsiderohet i anuluar dhe asnjë operacion në të nuk lejohet:
=> ZGJIDH * NGA t;
ERROR: current transaction is aborted, commands ignored until end of transaction block
Dhe madje nëse provoni të konfirmoni ndryshimet, PostgreSQL do të raportojë anulimin:
=> ANGAZHO;
ROLLBACK
Pse nuk mund tĂ« vazhdojmĂ« ekzekutimin e transaksionit pas njĂ« dĂ«shtimi? Arsyeja Ă«shtĂ« se gabimi mund tĂ« ndodhi nĂ« njĂ« mĂ«nyrĂ« qĂ« ne do tĂ« kishim qasje nĂ« njĂ« pjesĂ« tĂ« ndryshimeve â do tĂ« ishte shkelur atomikĂ«sia jo vetĂ«m e transaksionit, por edhe e operatorit. Siç ndodh nĂ« shembullin tonĂ«, ku operatori kishte arritur tĂ« azhurnonte njĂ« rresht para gabimit:
=> SELEKTO * NGA heap_page('t',0);
ctid | gjendja | xmin | xmax | t_ctid
-------+--------+----------+-------+--------
(0,1) | normal | 3669 (c) | 3672 | (0,4)
(0,2) | normal | 3670 (a) | 0 (a) | (0,2)
(0,3) | normal | 3671 (c) | 0 (a) | (0,3)
(0,4) | normal | 3672 | 0 (a) | (0,4)
(4 rreshta)
Duhet thënë se në psql ekziston një mod që ndihmon për të vazhduar punën e transaksionit pas një dështimi siç do ishte nëse veprimet e gabuar të operatorit do të anulloheshin.
=> set ON_ERROR_ROLLBACK on
=> BEGIN;
=> SELECT * FROM t;
id | s
----+-----
2 | FOO
4 | BAR
(2 rows)
=> UPDATE t SET s = repeat('X', 1/(id-4));
ERROR: division by zero
=> ZGJIDH * NGA t;
id | s
----+-----
2 | FOO
4 | BAR
(2 rows)
=> ANGAZHO;
Nuk është e vështirë të bësh një supozim se në një mod të tillë psql në fakt vendos një pikë ruajtjeje të pandërgjegjshme para çdo komande dhe në rast dështimi fillon rihapjen në të. Ky mod nuk përdoret si standard, pasi vendosja e pikave të ruajtjes (edhe pa rikthimin në to) ka kostot e saj të konsiderueshme.
Burimi: habr.com
