MVCC-3. Versionet e vargut

Pra, ne kemi shqyrtuar çështje që lidhen me izolim, dhe bëri një tërheqje rreth organizimi i të dhënave në një nivel të ulët. Dhe më në fund arritëm në pjesën më interesante - versionet e vargjeve.

Titulli

Siç kemi thënë tashmë, çdo rresht mund të ekzistojë njëkohësisht në disa versione në bazën e të dhënave. Një version duhet të dallohet disi nga tjetri. Për këtë qëllim, çdo version ka dy shenja që përcaktojnë "kohën" e veprimit të këtij versioni (xmin dhe xmax). Në thonjëza - sepse nuk është koha si e tillë që përdoret, por një numërues i veçantë në rritje. Dhe ky numërues është numri i transaksionit.

(Si zakonisht, realiteti është më i ndërlikuar: numri i transaksionit nuk mund të rritet gjatë gjithë kohës për shkak të kapacitetit të kufizuar të biteve të sportelit. Por ne do t'i shikojmë këto detaje në detaje kur të arrijmë në ngrirje.)

Kur krijohet një rresht, xmin vendoset në numrin e transaksionit që ka lëshuar komandën INSERT dhe xmax lihet bosh.

Kur një rresht fshihet, vlera xmax e versionit aktual shënohet me numrin e transaksionit që ka kryer DELETE.

Kur një rresht modifikohet nga një komandë UPDATE, në të vërtetë kryhen dy operacione: DELETE dhe INSERT. Versioni aktual i rreshtit vendos xmax të barabartë me numrin e transaksionit që ka kryer UPDATE. Më pas krijohet një version i ri i të njëjtit varg; vlera e tij xmin përkon me vlerën xmax të versionit të mëparshëm.

Fushat xmin dhe xmax përfshihen në kokën e versionit të rreshtit. Përveç këtyre fushave, titulli përmban të tjera, për shembull:

  • infomask është një seri bitësh që përcaktojnë vetitë e këtij versioni. Ka mjaft prej tyre; Ne gradualisht do të shqyrtojmë ato kryesore.
  • ctid është një lidhje me versionin tjetër, më të ri të së njëjtës linjë. Për versionin më të ri, më aktual të një vargu, ctid i referohet vetë këtij versioni. Numri ka formën (x,y), ku x është numri i faqes, y është numri i indeksit në grup.
  • null bitmap - Shënon ato kolona të një versioni të caktuar që përmbajnë një vlerë null (NULL). NULL nuk është një nga vlerat normale të tipit të të dhënave, kështu që atributi duhet të ruhet veçmas.

Si rezultat, kreu është mjaft i madh - të paktën 23 bajt për çdo version të linjës, dhe zakonisht më shumë për shkak të bitmap NULL. Nëse tabela është "e ngushtë" (d.m.th., përmban pak kolona), shpenzimet e përgjithshme mund të marrin më shumë se informacioni i dobishëm.

futur

Le të hedhim një vështrim më të afërt se si kryhen operacionet e vargut të nivelit të ulët, duke filluar me futjen.

Për eksperimente, le të krijojmë një tabelë të re me dy kolona dhe një indeks në njërën prej tyre:

=> CREATE TABLE t(
  id serial,
  s text
);
=> CREATE INDEX ON t(s);

Le të fusim një rresht pas fillimit të një transaksioni.

=> BEGIN;
=> INSERT INTO t(s) VALUES ('FOO');

Këtu është numri ynë aktual i transaksionit:

=> SELECT txid_current();
 txid_current 
--------------
         3664
(1 row)

Le të shohim përmbajtjen e faqes. Funksioni heap_page_items i shtesës pageinspect ju lejon të merrni informacion në lidhje me treguesit dhe versionet e rreshtave:

=> SELECT * FROM heap_page_items(get_raw_page('t',0)) gx
-[ RECORD 1 ]-------------------
lp          | 1
lp_off      | 8160
lp_flags    | 1
lp_len      | 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

Vini re se fjala grumbull në PostgreSQL u referohet tabelave. Ky është një tjetër përdorim i çuditshëm i termit - dihet një grumbull struktura e të dhënave, e cila nuk ka asgjë të përbashkët me tabelën. Këtu fjala përdoret në kuptimin e "gjithçka është hedhur së bashku", në krahasim me indekset e renditura.

Funksioni tregon të dhënat "siç janë", në një format që është i vështirë për t'u kuptuar. Për ta kuptuar atë, ne do të lëmë vetëm një pjesë të informacionit dhe do ta deshifrojmë atë:

=> SELECT '(0,'||lp||')' AS ctid,
       CASE lp_flags
         WHEN 0 THEN 'unused'
         WHEN 1 THEN 'normal'
         WHEN 2 THEN 'redirect to '||lp_off
         WHEN 3 THEN 'dead'
       END AS state,
       t_xmin as xmin,
       t_xmax as xmax,
       (t_infomask & 256) > 0  AS xmin_commited,
       (t_infomask & 512) > 0  AS xmin_aborted,
       (t_infomask & 1024) > 0 AS xmax_commited,
       (t_infomask & 2048) > 0 AS xmax_aborted,
       t_ctid
FROM heap_page_items(get_raw_page('t',0)) gx
-[ RECORD 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ë bëmë:

  • Shtoi një zero në numrin e indeksit për ta bërë atë të duket njësoj si t_ctid: (numri i faqes, numri i indeksit).
  • Deshifroi gjendjen e treguesit lp_flags. Këtu është "normale" - kjo do të thotë që treguesi në të vërtetë i referohet versionit të vargut. Do të shohim kuptime të tjera më vonë.
  • Nga të gjitha pjesët e informacionit, vetëm dy palë janë identifikuar deri më tani. Bitet xmin_committed dhe xmin_aborted tregojnë nëse numri i transaksionit xmin është kryer (i ndërprerë). Dy bit të ngjashëm i referohen numrit të transaksionit xmax.

Çfarë shohim? Kur futni një rresht, një indeks numër 1 do të shfaqet në faqen e tabelës, duke treguar versionin e parë dhe të vetëm të rreshtit.

Në versionin string, fusha xmin është e mbushur me numrin aktual të transaksionit. Transaksioni është ende aktiv, kështu që të dy bitet 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 një version më i ri nuk ekziston.

Fusha xmax është e mbushur me një numër dummy 0 sepse ky version i rreshtit nuk është fshirë dhe është aktual. Transaksionet nuk do t'i kushtojnë vëmendje këtij numri sepse biti xmax_aborted është vendosur.

Le të bëjmë një hap më shumë drejt përmirësimit të lexueshmërisë duke shtuar bit informacioni në numrat e transaksioneve. Dhe le të krijojmë një funksion, pasi do të na duhet kërkesa më shumë se një herë:

=> CREATE FUNCTION heap_page(relname text, pageno integer)
RETURNS TABLE(ctid tid, state text, xmin text, xmax text, t_ctid tid)
AS $$
SELECT (pageno,lp)::text::tid AS ctid,
       CASE lp_flags
         WHEN 0 THEN 'unused'
         WHEN 1 THEN 'normal'
         WHEN 2 THEN 'redirect to '||lp_off
         WHEN 3 THEN 'dead'
       END AS state,
       t_xmin || CASE
         WHEN (t_infomask & 256) > 0 THEN ' (c)'
         WHEN (t_infomask & 512) > 0 THEN ' (a)'
         ELSE ''
       END AS xmin,
       t_xmax || CASE
         WHEN (t_infomask & 1024) > 0 THEN ' (c)'
         WHEN (t_infomask & 2048) > 0 THEN ' (a)'
         ELSE ''
       END AS xmax,
       t_ctid
FROM heap_page_items(get_raw_page(relname,pageno))
ORDER BY lp;
$$ LANGUAGE SQL;

Në këtë formë, është shumë më e qartë se çfarë po ndodh në kokën e versionit të rreshtit:

=> SELECT * FROM heap_page('t',0);
 ctid  | state  | xmin | xmax  | t_ctid 
-------+--------+------+-------+--------
 (0,1) | normal | 3664 | 0 (a) | (0,1)
(1 row)

Informacione të ngjashme, por dukshëm më pak të detajuara, mund të merren nga vetë tabela, duke përdorur pseudo-kolona xmin dhe xmax:

=> SELECT xmin, xmax, * FROM t;
 xmin | xmax | id |  s  
------+------+----+-----
 3664 |    0 |  1 | FOO
(1 row)

fiksim

Nëse një transaksion është përfunduar me sukses, duhet të mbani mend statusin e tij - vini re se ai është kryer. Për ta bërë këtë, përdoret një strukturë e quajtur XACT (dhe para versionit 10 quhej CLOG (regjistri i kryerjes) dhe ky emër mund të gjendet ende në vende të ndryshme).

XACT nuk është një tabelë e katalogut të sistemit; këto janë skedarët në drejtorinë PGDATA/pg_xact. Ata kanë dy bit të alokuar për çdo transaksion: të kryer dhe të ndërprerë - ashtu si në kokën e versionit të rreshtit. Ky informacion është i ndarë në disa skedarë vetëm për lehtësi; ne do t'i kthehemi kësaj çështje kur të shqyrtojmë ngrirjen. Dhe puna me këto skedarë kryhet faqe pas faqe, si me të gjithë të tjerët.

Pra, kur një transaksion kryhet në XACT, biti i kryer vendoset për këtë transaksion. Dhe kjo është gjithçka që ndodh gjatë kryerjes (edhe pse ne nuk po flasim ende për regjistrin e para-regjistrimit).

Kur një transaksion tjetër hyn në faqen e tabelës që sapo shikuam, ai do të duhet t'i përgjigjet disa pyetjeve.

  1. A ka përfunduar transaksioni xmin? Nëse jo, atëherë versioni i krijuar i vargut nuk duhet të jetë i dukshëm.
    Ky kontroll kryhet duke parë një strukturë tjetër, e cila ndodhet në memorien e përbashkët të shembullit dhe quhet ProcArray. Ai përmban një listë të të gjitha proceseve aktive, dhe për secilin tregohet numri i transaksionit të tij aktual (aktiv).
  2. Nëse mbaroi, atëherë si - duke kryer apo anuluar? Nëse anulohet, atëherë as versioni i rreshtit nuk duhet të jetë i dukshëm.
    Kjo është pikërisht ajo për të cilën është XACT. Por, megjithëse faqet e fundit të XACT ruhen në buferë në RAM, është ende e shtrenjtë të kontrollosh XACT çdo herë. Prandaj, pasi të përcaktohet statusi i transaksionit, ai shkruhet në bitet xmin_committed dhe xmin_aborted të versionit të vargut. Nëse një nga këta bit është vendosur, atëherë gjendja e transaksionit xmin konsiderohet e njohur dhe transaksioni tjetër nuk do të duhet të ketë akses në XACT.

Pse këto bit nuk vendosen nga vetë transaksioni duke bërë futjen? Kur ndodh një insert, transaksioni nuk e di ende nëse do të ketë sukses. Dhe në momentin e kryerjes, nuk është më e qartë se cilat rreshta në cilat faqe janë ndryshuar. Mund të ketë shumë faqe të tilla, dhe memorizimi i tyre është i padobishëm. Përveç kësaj, disa faqe mund të nxirren nga memoria e tamponit në disk; leximi i tyre përsëri për të ndryshuar pjesët do të ngadalësonte ndjeshëm kryerjen.

Ana negative e kursimeve është se pas ndryshimeve, çdo transaksion (qoftë edhe ai që kryen një lexim të thjeshtë - SELECT) mund të fillojë të ndryshojë faqet e të dhënave në cache-in e tamponit.

Pra, le të rregullojmë ndryshimin.

=> COMMIT;

Asgjë nuk ka ndryshuar në faqe (por ne e dimë që statusi i transaksionit është regjistruar tashmë në XACT):

=> SELECT * FROM heap_page('t',0);
 ctid  | state  | xmin | xmax  | t_ctid 
-------+--------+------+-------+--------
 (0,1) | normal | 3664 | 0 (a) | (0,1)
(1 row)

Tani transaksioni që hyn në faqe i pari do të duhet të përcaktojë statusin e transaksionit xmin dhe ta shkruajë atë në bitet e informacionit:

=> SELECT * FROM t;
 id |  s  
----+-----
  1 | FOO
(1 row)

=> SELECT * FROM heap_page('t',0);
 ctid  | state  |   xmin   | xmax  | t_ctid 
-------+--------+----------+-------+--------
 (0,1) | normal | 3664 (c) | 0 (a) | (0,1)
(1 row)

Largim

Kur një rresht fshihet, numri i transaksionit aktual të fshirjes shkruhet në fushën xmax të versionit aktual dhe biti xmax_aborted pastrohet.

Vini re se vlera e caktuar e xmax që korrespondon me transaksionin aktiv vepron si një bllokim rreshti. Nëse një transaksion tjetër dëshiron të përditësojë ose fshijë këtë rresht, ai do të detyrohet të presë që transaksioni xmax të përfundojë. Për bllokimin do të flasim më vonë. Tani për tani, ne vetëm vërejmë se numri i bllokimeve të rreshtave është i pakufizuar. Ata nuk zënë hapësirë ​​në RAM dhe performanca e sistemit nuk vuan nga numri i tyre. Vërtetë, transaksionet "të gjata" kanë disavantazhe të tjera, por më shumë për këtë më vonë.

Le të fshijmë rreshtin.

=> BEGIN;
=> DELETE FROM t;
=> SELECT txid_current();
 txid_current 
--------------
         3665
(1 row)

Ne shohim që numri i transaksionit është shkruar në fushën xmax, por bitet e informacionit nuk janë vendosur:

=> SELECT * FROM heap_page('t',0);
 ctid  | state  |   xmin   | xmax | t_ctid 
-------+--------+----------+------+--------
 (0,1) | normal | 3664 (c) | 3665 | (0,1)
(1 row)

Anulim

Ndërprerja e ndryshimeve funksionon në mënyrë të ngjashme me kryerjen, vetëm në XACT biti i ndërprerë vendoset për transaksionin. Zhbërja është po aq e shpejtë sa edhe kryerja. Edhe pse komanda quhet ROLLBACK, ndryshimet nuk kthehen prapa: gjithçka që transaksioni arriti të ndryshojë në faqet e të dhënave mbetet e pandryshuar.

=> ROLLBACK;
=> SELECT * FROM heap_page('t',0);
 ctid  | state  |   xmin   | xmax | t_ctid 
-------+--------+----------+------+--------
 (0,1) | normal | 3664 (c) | 3665 | (0,1)
(1 row)

Kur të hapet faqja, statusi do të kontrollohet dhe biti i këshillës xmax_aborted do të vendoset në versionin e rreshtit. Vetë numri xmax mbetet në faqe, por askush nuk do ta shikojë atë.

=> SELECT * FROM t;
 id |  s  
----+-----
  1 | FOO
(1 row)

=> SELECT * FROM heap_page('t',0);
 ctid  | state  |   xmin   |   xmax   | t_ctid 
-------+--------+----------+----------+--------
 (0,1) | normal | 3664 (c) | 3665 (a) | (0,1)
(1 row)

Update

Përditësimi funksionon sikur fillimisht fshiu versionin aktual të rreshtit dhe më pas futi një të ri.

=> BEGIN;
=> UPDATE t SET s = 'BAR';
=> SELECT txid_current();
 txid_current 
--------------
         3666
(1 row)

Kërkesa prodhon një rresht (versioni i ri):

=> SELECT * FROM t;
 id |  s  
----+-----
  1 | BAR
(1 row)

Por në faqe ne shohim të dy versionet:

=> SELECT * FROM heap_page('t',0);
 ctid  | state  |   xmin   | xmax  | t_ctid 
-------+--------+----------+-------+--------
 (0,1) | normal | 3664 (c) | 3666  | (0,2)
 (0,2) | normal | 3666     | 0 (a) | (0,2)
(2 rows)

Versioni i fshirë shënohet me numrin aktual të transaksionit në fushën xmax. Për më tepër, kjo vlerë shkruhet mbi të vjetrën, pasi transaksioni i mëparshëm është anuluar. Dhe biti xmax_aborted pastrohet sepse statusi i transaksionit aktual nuk dihet ende.

Versioni i parë i linjës tani i referohet të dytës (fusha t_ctid) si më e reja.

Një indeks i dytë shfaqet në faqen e indeksit dhe një rresht i dytë i referohet versionit të dytë në faqen e tabelës.

Ashtu si me fshirjen, vlera xmax në versionin e parë të rreshtit është një tregues se rreshti është i kyçur.

Epo, le të përfundojmë transaksionin.

=> COMMIT;

Tregues

Deri tani kemi folur vetëm për faqet e tabelave. Çfarë ndodh brenda indekseve?

Informacioni në faqet e indeksit ndryshon shumë në varësi të llojit specifik të indeksit. Dhe madje edhe një lloj indeksi ka lloje të ndryshme faqesh. Për shembull, një pemë B ka një faqe meta të dhënash dhe faqe "të rregullta".

Megjithatë, faqja zakonisht ka një grup treguesish për rreshtat dhe vetë rreshtat (ashtu si një faqe tabele). Përveç kësaj, në fund të faqes ka hapësirë ​​për të dhëna të veçanta.

Rreshtat në indekse mund të kenë gjithashtu struktura shumë të ndryshme në varësi të llojit të indeksit. Për shembull, për një pemë B, rreshtat që lidhen me faqet e fletëve 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ë strukturohet në një mënyrë krejtësisht të ndryshme.

Pika më e rëndësishme është se nuk ka versione të rreshtave në indekset e çdo lloji. Epo, ose mund të supozojmë se çdo rresht përfaqësohet saktësisht nga një version. Me fjalë të tjera, nuk ka fusha xmin dhe xmax në kokën e rreshtit të indeksit. Mund të supozojmë se lidhjet nga indeksi çojnë në të gjitha versionet e tabelës së rreshtave - kështu që ju mund të kuptoni se cilin version do të shohë transaksioni vetëm duke parë tabelën. (Si gjithmonë, kjo nuk është e gjithë e vërteta. Në disa raste, harta e dukshmërisë mund të optimizojë procesin, por ne do ta shikojmë këtë më në detaje më vonë.)

Në të njëjtën kohë, në faqen e indeksit gjejmë tregues për të dy versionet, si atë aktual ashtu edhe atë të vjetër:

=> SELECT itemoffset, ctid FROM bt_page_items('t_s_idx',1);
 itemoffset | ctid  
------------+-------
          1 | (0,2)
          2 | (0,1)
(2 rows)

Transaksionet virtuale

Në praktikë, PostgreSQL përdor optimizime që e lejojnë atë të "ruajë" numrat e transaksioneve.

Nëse një transaksion lexon vetëm të dhëna, ai nuk ka asnjë efekt në dukshmërinë e versioneve të rreshtit. Prandaj, procesi i shërbimit së pari lëshon një xid virtual në transaksion. Numri përbëhet nga një ID e procesit dhe një numër sekuence.

Lëshimi i këtij numri nuk kërkon sinkronizim midis të gjitha proceseve dhe për këtë arsye është shumë i shpejtë. Me një arsye tjetër të përdorimit të numrave virtualë do të njihemi kur flasim për ngrirjen.

Numrat virtualë nuk merren parasysh në asnjë mënyrë në fotografitë e të dhënave.

Në momente të ndryshme kohore, mund të ketë transaksione virtuale në sistem me numra që tashmë janë përdorur, dhe kjo është normale. Por një numër i tillë nuk mund të shkruhet në faqet e të dhënave, sepse herën tjetër që të aksesohet faqja mund të humbasë çdo kuptim.

=> BEGIN;
=> SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                         
(1 row)

Nëse një transaksion fillon të ndryshojë të dhënat, atij i jepet një numër real, unik i transaksionit.

=> UPDATE accounts SET amount = amount - 1.00;
=> SELECT txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                     3667
(1 row)

=> COMMIT;

Transaksionet e ndërlidhura

Ruaj pikë

Përcaktuar në SQL kurseni pikë (savepoint), të cilat ju lejojnë të anuloni një pjesë të një transaksioni pa e ndërprerë atë plotësisht. Por kjo nuk përshtatet në diagramin e mësipërm, pasi transaksioni ka të njëjtin status për të gjitha ndryshimet e tij dhe fizikisht asnjë e dhënë nuk rikthehet.

Për të zbatuar këtë funksionalitet, një transaksion me një pikë kursimi ndahet në disa të veçanta transaksionet e mbivendosura (nëntransaksion), statusi i të cilit mund të menaxhohet veçmas.

Transaksionet e ndërlidhura kanë numrin e tyre (më të lartë se numri i transaksionit kryesor). Statusi i transaksioneve të ndërlidhura regjistrohet në mënyrën e zakonshme në XACT, por statusi përfundimtar varet nga statusi i transaksionit kryesor: nëse ai anulohet, atëherë anulohen të gjitha transaksionet e ndërlidhura.

Informacioni rreth foleve të transaksioneve ruhet në skedarë në drejtorinë PGDATA/pg_subtrans. Skedarët aksesohen përmes buferave në kujtesën e përbashkët të shembullit, të organizuar në të njëjtën mënyrë si buferat XACT.

Mos i ngatërroni transaksionet e ndërlidhura me transaksionet autonome. Transaksionet autonome nuk varen nga njëra-tjetra në asnjë mënyrë, por transaksionet e ndërlidhura varen. Nuk ka transaksione autonome në PostgreSQL të rregullt dhe, ndoshta, për të mirën: ato nevojiten shumë, shumë rrallë, dhe prania e tyre në DBMS-të e tjera provokon abuzim, nga i cili më pas vuajnë të gjithë.

Le të pastrojmë tabelën, të fillojmë një transaksion dhe të fusim rreshtin:

=> TRUNCATE TABLE t;
=> BEGIN;
=> INSERT INTO t(s) VALUES ('FOO');
=> SELECT txid_current();
 txid_current 
--------------
         3669
(1 row)

=> SELECT xmin, xmax, * FROM t;
 xmin | xmax | id |  s  
------+------+----+-----
 3669 |    0 |  2 | FOO
(1 row)

=> SELECT * FROM heap_page('t',0);
 ctid  | state  | xmin | xmax  | t_ctid 
-------+--------+------+-------+--------
 (0,1) | normal | 3669 | 0 (a) | (0,1)
(1 row)

Tani le të vendosim një pikë ruajtjeje dhe të fusim 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() kthen numrin kryesor të transaksionit, jo numrin e transaksionit të ndërlidhur.

=> SELECT xmin, xmax, * FROM t;
 xmin | xmax | id |  s  
------+------+----+-----
 3669 |    0 |  2 | FOO
 3670 |    0 |  3 | XYZ
(2 rows)

=> SELECT * FROM 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)

Le të kthehemi në pikën e ruajtjes dhe të fusim 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)

=> SELECT * FROM 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 ne vazhdojmë të shohim rreshtin e shtuar nga transaksioni i ndërlidhur i anuluar.

Ne rregullojmë ndryshimet.

=> COMMIT;
=> SELECT xmin, xmax, * FROM t;
 xmin | xmax | id |  s  
------+------+----+-----
 3669 |    0 |  2 | FOO
 3671 |    0 |  4 | BAR
(2 rows)

=> SELECT * FROM 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 mund të shihni qartë se çdo transaksion i ndërlidhur ka statusin e vet.

Vini re se transaksionet e ndërlidhura nuk mund të përdoren në mënyrë eksplicite në SQL, domethënë, nuk mund të filloni një transaksion të ri pa përfunduar atë aktual. Ky mekanizëm aktivizohet në mënyrë implicite kur përdorni pikat e ruajtjes, si dhe kur trajtoni përjashtime PL/pgSQL dhe në një numër rastesh të tjera, më ekzotike.

=> BEGIN;
BEGIN
=> BEGIN;
WARNING:  there is already a transaction in progress
BEGIN
=> COMMIT;
COMMIT
=> COMMIT;
WARNING:  there is no transaction in progress
COMMIT

Gabimet dhe atomiciteti i operacioneve

Çfarë ndodh nëse ndodh një gabim gjatë kryerjes së një operacioni? Për shembull, si kjo:

=> 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

Një gabim ka ndodhur. Tani transaksioni konsiderohet i ndërprerë dhe asnjë operacion nuk lejohet në të:

=> SELECT * FROM t;
ERROR:  current transaction is aborted, commands ignored until end of transaction block

Dhe edhe nëse përpiqeni të kryeni ndryshimet, PostgreSQL do të raportojë një ndërprerje:

=> COMMIT;
ROLLBACK

Pse një transaksion nuk mund të vazhdojë pas një dështimi? Fakti është se një gabim mund të lindë në atë mënyrë që ne të fitojmë akses në një pjesë të ndryshimeve - atomiciteti i as transaksionit, por i operatorit do të cenohej. Si në shembullin tonë, ku operatori arriti të përditësojë një linjë përpara gabimit:

=> SELECT * FROM heap_page('t',0);
 ctid  | state  |   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 rows)

Duhet thënë se psql ka një modalitet që ende lejon që transaksioni të vazhdojë pas një dështimi sikur veprimet e operatorit të gabuar të ishin kthyer prapa.

=> 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

=> SELECT * FROM t;
 id |  s  
----+-----
  2 | FOO
  4 | BAR
(2 rows)

=> COMMIT;

Nuk është e vështirë të merret me mend se në këtë mënyrë, psql në fakt vendos një pikë të nënkuptuar të ruajtjes përpara çdo komande dhe në rast dështimi fillon një rikthim në të. Ky modalitet nuk përdoret si parazgjedhje, pasi vendosja e pikave të ruajtjes (edhe pa u kthyer në to) përfshin shpenzime të konsiderueshme.

vazhdimi.

Burimi: www.habr.com

Shto një koment