MVCC-3. Verzie riadkov

Tak sme sa zaoberali otázkami týkajúcimi sa izolácie, a urobili sme odbočku o organizácii dát na nízkej úrovni. A nakoniec sme sa dostali k najzaujímavejšiemu — k verziám riadkov.

Nadpis

Ako už bolo spomenuté, každý riadok môže byť súčasne prítomný v databáze v niekoľkých verziách. Jednu verziu od druhej treba nejako odlíšiť. Na tento účel má každá verzia dve značky, ktoré určujú "čas" platnosti danej verzie (xmin a xmax). V úvodzovkách — pretože sa nepoužíva čas ako taký, ale špeciálny zvyšujúci sa čítač. A tento čítač je číslom transakcie.

(Ako zvyčajne, naozaj je to všetko zložitejšie: číslo transakcií nemôže neustále narastať kvôli obmedzenému rozsahu čítača. Ale tieto detaily podrobne preskúmame, keď sa dostaneme k zamrznutiu.)

Keď sa riadok vytvorí, hodnota xmin sa nastaví na číslo transakcie, ktorá vykonala príkaz INSERT, a xmax nie je vyplnené.

Keď sa riadok odstráni, hodnota xmax aktuálnej verzie je označená číslom transakcie, ktorá vykonala DELETE.

Keď sa riadok zmení príkazom UPDATE, v skutočnosti sa vykonajú dve operácie: DELETE a INSERT. V aktuálnej verzii riadku sa nastaví xmax, rovná číslu transakcie, ktorá vykonala UPDATE. Potom sa vytvorí nová verzia toho istého riadku; hodnota xmin jej zodpovedá hodnote xmax predchádzajúcej verzie.

Polia xmin a xmax sú súčasťou hlavičky verzie riadku. Okrem týchto polí hlavička obsahuje aj ďalšie, napríklad:

  • infomask — rad bitov, ktoré určujú vlastnosti danej verzie. Je ich celkom dosť; základné z nich postupne preskúmame.
  • ctid — odkaz na ďalšiu, novšiu verziu toho istého riadku. Pri najnovšej, aktuálnej, verzii riadku sa ctid odkazuje na túto verziu. Číslo má tvar (x,y), kde x je číslo stránky, y je poradové číslo ukazovateľa v poli.
  • bitová mapa neurčitých hodnôt — označuje tie stĺpce danej verzie, ktoré obsahujú neurčitú hodnotu (NULL). NULL nie je jednou z bežných hodnôt typov dát, preto je potrebné uchovávať tento znak osobitne.

V dôsledku toho je hlavička dosť veľká — minimálne 23 bajtov na každú verziu riadku, a zvyčajne viac kvôli bitovej mape NULL. Ak je tabuľka "úzka" (t.j. obsahuje málo stĺpcov), náklady môžu zaberajú viac než užitočné informácie.

Vloženie

Pozrime sa podrobne, ako prebiehajú operácie so znakmi na nízkej úrovni, a začnime vložením.

Na experimenty vytvoríme novú tabuľku s dvoma stĺpcami a indexom podľa jedného z nich:

=> VYTVORIŤ TABUĽKU t(
  id serial,
  s text
);
=> VYTVORIŤ INDEX NA t(s);

Vložíme jeden riadok, pričom predtým začneme transakciu.

=> ZAČAŤ;
=> VLOŽIŤ DO t(s) HODNOTY ('FOO');

Tu je číslo našej aktuálnej transakcie:

=> VYBRAŤ txid_current();
 txid_current 
--------------
         3664
(1 riadok)

Pozrime sa na obsah stránky. Funkcia heap_page_items rozšírenia pageinspect umožňuje získať informácie o ukazovateľoch a verziách riadkov:

=> VYBRAŤ * Z heap_page_items(get_raw_page('t',0)) gx
-[ ZÁZNAM 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

Všimnite si, že slovom heap (hromada) sa v PostgreSQL označujú tabuľky. Toto je ďalšie zvláštne použitie tohto termínu — hromada je známa dátová štruktúra, ktorá nemá s tabuľkou nič spoločného. Tu sa toto slovo používa v zmysle „všetko sa nahromadilo do hromady“, na rozdiel od usporiadaných indexov.

Funkcia zobrazuje údaje „tak, ako sú“, v formáte, ktorý je zložitý na porozumenie. Aby sme to pochopili, ponecháme si len časť informácií a rozšifrujeme ich:

=> VYBRAŤ '(0,'||lp||')' AKO ctid,
       PRÍPAD lp_flags
         KEĎ 0 POTOM 'nepoužité'
         KEĎ 1 POTOM 'normálne'
         KEĎ 2 POTOM 'presmerovanie na '||lp_off
         KEĎ 3 POTOM 'mŕtve'
       KONIEC AKO stav,
       t_xmin ako xmin,
       t_xmax ako xmax,
       (t_infomask & 256) > 0  AKO xmin_commited,
       (t_infomask & 512) > 0  AKO xmin_aborted,
       (t_infomask & 1024) > 0 AKO xmax_commited,
       (t_infomask & 2048) > 0 AKO xmax_aborted,
       t_ctid
Z heap_page_items(get_raw_page('t',0)) gx
-[ ZÁZNAM 1 ]-+-------
ctid          | (0,1)
stav          | normálne
xmin          | 3664
xmax          | 0
xmin_commited | f
xmin_aborted  | f
xmax_commited | f
xmax_aborted  | t
t_ctid        | (0,1)

Tu je to, čo sme urobili:

  • Pridali sme k číslu ukazovateľa nulu, aby sme ho dostali do rovnakého formátu ako t_ctid: (číslo stránky, číslo ukazovateľa).
  • Rozšifrovali sme stav ukazovateľa lp_flags. Tu je „normálny“ — to znamená, že ukazovateľ sa skutočne odvoláva na verziu riadka. Ostatné hodnoty preskúmame neskôr.
  • Z všetkých informačných bitov sme zatiaľ vybrali len dve dvojice. Bity xmin_committed a xmin_aborted ukazujú, či bola transakcia s číslom xmin potvrdená (zrušená). Dve podobné bity sa týkajú transakcie s číslom xmax.

Čo vidíme? Pri vložení riadku na tabuľkovú stránku sa objaví ukazovateľ s číslom 1, ktorý odkazuje na prvú a jedinú verziu riadku.

Vo verzii riadku je pole xmin vyplnené číslom aktuálneho transakcie. Transakcia je ešte aktívna, takže oba bity xmin_committed a xmin_aborted nie sú nastavené.

Pole ctid verzie riadku odkazuje na tento rovnaký riadok. To znamená, že novšia verzia neexistuje.

Pole xmax je vyplnené fiktívnym číslom 0, pretože táto verzia riadku nie je vymazaná a je aktuálna. Transakcie nebudú brať ohľad na toto číslo, pretože je nastavený bit xmax_aborted.

Urobme ešte jeden krok k zlepšeniu čitateľnosti, dopíšeme informačné bity k číslam transakcií. A vytvoríme funkciu, pretože dopyt nám ešte raz poslúži:

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

V tejto podobe je výrazne jasnejšie, čo sa deje v hlavičke verzie riadku:

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

Podobné, ale podstatne menej podrobné, informácie možno získať aj z samotnej tabuľky pomocou pseudostlpcov xmin a xmax:

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

Zápis

Pri úspešnom dokončení transakcie je potrebné zapamätať si jej stav — označiť, že bola potvrdená. Na tento účel sa používa štruktúra nazývaná XACT (a do verzie 10 sa nazývala CLOG (commit log) a tento názov sa môže stále objavovať na rôznych miestach).

XACT nie je tabuľka systémového katalógu; ide o súbory v adresári PGDATA/pg_xact. V nich je pre každú transakciu vyhradené dva bity: committed a aborted — presne tak, ako v hlavičke verzie riadku. Informácie sú rozdelené do niekoľkých súborov čisto pre pohodlie, k tomuto problému sa ešte vrátime, keď budeme skúmať zamrznutie. Práca s týmito súbormi prebieha po stránkach, rovnako ako so všetkými ostatnými.

Pri zapisovaní transakcie v XACT sa nastaví bit committed pre danú transakciu. A to je všetko, čo sa deje pri zápise (pravda, zatiaľ nehovoríme o predzáznamovej knihe).

Keď sa akákoľvek iná transakcia pokúsi získať prístup k stránke tabuľky, na ktorú sme sa práve pozerali, bude musieť odpovedať na niekoľko otázok.

  1. Skončila sa transakcia xmin? Ak nie, vytvorená verzia riadku by nemala byť viditeľná.
    Tento test sa vykonáva pohľadom na inštruktúru nachádzajúcu sa v zdieľanej pamäti inštancie, nazývanú ProcArray. Obsahuje zoznam všetkých aktívnych procesov a pre každý z nich je uvedené číslo jeho aktuálnej (aktívnej) transakcie.
  2. Ak sa skončila, tak ako — potvrdením alebo zrušením? Ak zrušením, verzia riadku by tiež nemala byť viditeľná.
    Na to presne je potrebný XACT. Napriek tomu, že posledné stránky XACT sa uchovávajú v pamäti, pri každej kontrole XACT dochádza k dodatočnej záťaži. Preto sa raz zistený stav transakcie zapisuje do bitov xmin_committed a xmin_aborted verzie riadku. Ak je jeden z týchto bitov nastavený, stav transakcie xmin sa považuje za známy a nasledujúcej transakcii už nebude musieť pristupovať k XACT.

Prečo tieto bity nenastavuje samotná transakcia vykonávajúca vloženie? Pri vkladaní transakcia ešte nevie, či sa dokončí úspešne. A v čase potvrdenia už nie je jasné, ktoré konkrétne riadky v ktorých stránkach boli zmenené. Týchto stránok môže byť veľa a pamätať si ich nie je výhodné. Navyše niektoré stránky môžu byť vytesnené z cache na disk; znovu ich čítať na zmenu bitov by znamenalo výrazne spomaliť potvrdenie.

Druhá strana úspor spočíva v tom, že po zmenách môže akákoľvek transakcia (aj tá, ktorá vykonáva jednoduché čítanie — SELECT) začať meniť stránky dát v pamäti.

Takže zaznamenajme zmenu.

=> COMMIT;

Na stránke sa nič nezmenilo (ale vieme, že stav transakcie je už zaznamenaný v XACT):

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

Teraz transakcia, ktorá sa prvá dostala na stránku, musí zistiť stav transakcie xmin a zapíše ho do informačných bitov:

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

Odstránenie

Pri odstránení riadku sa do poľa xmax aktuálnej verzie zapisuje číslo aktuálnej mažúcej transakcie a bit xmax_aborted sa resetuje.

Všimnite si, že nastavená hodnota xmax, ktorá zodpovedá aktívnej transakcii, slúži ako zámok pre riadok. Ak sa iná transakcia pokúsi aktualizovať alebo odstrániť tento riadok, bude musieť počkať na ukončenie transakcie xmax. O zámkoch sa detailnejšie zmienime neskôr. Momentálne si len poznamenajme, že počet zámkov riadkov nie je nijako obmedzený. Nezaberajú miesto v operačnej pamäti a výkonnosť systému netrpí ich množstvom. Samozrejme, "dlhé" transakcie majú svoje iné nevýhody, ale o tom tiež neskôr.

Odstránime riadok.

=> ZAČAŤ;
=> ODSTRÁNIŤ Z t;
=> VYBRAŤ txid_current();
 txid_current 
--------------
         3665
(1 riadok)

Vidíme, že číslo transakcie sa zapísalo do poľa xmax, ale informačné bity nie sú nastavené:

=> SELECT * FROM heap_page('t',0);
 ctid  | stav   |   xmin   | xmax | t_ctid 
-------+--------+----------+------+--------
 (0,1) | normálny | 3664 (c) | 3665 | (0,1)
(1 riadok)

Zrušenie

Zrušenie zmien funguje analogicky k potvrdeniu, len v XACT pre transakciu sa nastaví bit aborted. Zrušenie sa vykonáva rovnako rýchlo ako potvrdenie. Hoci sa príkaz nazýva ROLLBACK, nedochádza k vráteniu zmien: všetko, čo transakcia stihla zmeniť na stránkach údajov, zostáva nezmenené.

=> ROLLBACK;
=> VYBRAŤ * Z heap_page('t',0);
 ctid  | stav   |   xmin   | xmax | t_ctid 
-------+--------+----------+------+--------
 (0,1) | normálny | 3664 (c) | 3665 | (0,1)
(1 riadok)

Pri prístupe na stránku bude skontrolovaný stav a do verzie riadku sa nastaví bit označenia xmax_aborted. Samotné číslo xmax zostáva na stránke, ale nikto ho už nebude sledovať.

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

=> SELECT * FROM heap_page('t',0);
 ctid  | stav   |   xmin   |   xmax   | t_ctid 
-------+--------+----------+----------+--------
 (0,1) | normálny | 3664 (c) | 3665 (a) | (0,1)
(1 riadok)

Update

Aktualizácia funguje tak, akoby sa najprv vykonalo odstránenie aktuálnej verzie riadku a potom vklad novej.

=> ZAČAŤ;
=> AKTUALIZOVAŤ t SET s = 'BAR';
=> VYBRAŤ txid_current();
 txid_current 
--------------
         3666
(1 riadok)

Dotaz vracia jeden riadok (novú verziu):

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

Ale na stránke vidíme obe verzie:

=> SELECT * FROM heap_page('t',0);
 ctid  | stav   |   xmin   | xmax  | t_ctid 
-------+--------+----------+-------+--------
 (0,1) | normálny | 3664 (c) | 3666  | (0,2)
 (0,2) | normálny | 3666     | 0 (a) | (0,2)
(2 riadky)

Odstránená verzia je označená číslom aktuálnej transakcie v poli xmax. A toto číslo je zapísané na staré, pretože predchádzajúca transakcia bola zrušená. Bit xmax_aborted bol resetovaný, pretože stav aktuálnej transakcie je ešte neznámy.

Prvá verzia riadku teraz odkazuje na druhú (pole t_ctid) ako na novšiu.

Na indexovej stránke sa objaví druhý ukazovateľ a druhý riadok, ktorý odkazuje na druhú verziu v tabuľkovej stránke.

Rovnako ako pri odstránení, hodnota xmax v prvej verzii riadku slúži ako indikátor, že riadok je zablokovaný.

A ukončíme transakciu.

=> COMMIT;

Indexy

Doteraz sme sa rozprávali len o tabuľkových stránkach. Čo sa však deje vo vnútri indexov?

Informácie na indexových stránkach silno závisia od konkrétneho typu indexu. A dokonca aj jeden typ indexu môže mať rôzne druhy stránok. Napríklad B-strom má stránku s metadátami a „bežné“ stránky.

Napriek tomu sa zvyčajne na stránke nachádza pole ukazovateľov na riadky a samotné riadky (rovnako, ako aj na tabuľkovej stránke). Okrem toho je na konci stránky vyhradené miesto pre špeciálne údaje.

Riadky v indexoch môžu mať tiež veľmi odlišnú štruktúru v závislosti od typu indexu. Napríklad pre B-strom riadky, ktoré sa týkajú listových strán, obsahujú hodnotu kľúča indexovania a odkaz (ctid) na zodpovedajúci riadok tabuľky. Vo všeobecnosti môže byť index usporiadaný úplne iným spôsobom.

Najdôležitejším bodom je to, že v indexoch akéhokoľvek typu nie sú verzie riadkov. Alebo si môžeme myslieť, že každý riadok je reprezentovaný presne jednou verziou. Inými slovami, v hlavičke indexového riadku nie sú polia xmin a xmax. Odkazy z indexu vedú na všetky tabuľkové verzie riadkov — takže zistiť, ktorú z verzií uvidí transakcia, môžeme len pohľadom do tabuľky. (Ako obvykle, to nie je celá pravda. V niektorých prípadoch karta viditeľnosti umožňuje optimalizovať proces, ale o tom sa podrobnejšie porozprávame neskôr.)

Pri tom na indexovej stránke nachádzame ukazovatele na obe verzie, ako na aktuálnu, tak aj na starú:

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

Virtuálne transakcie

V praxi PostgreSQL používa optimalizáciu, ktorá umožňuje „šetriť“ čísla transakcií.

Ak transakcia len číta dáta, tak nijako neovplyvňuje viditeľnosť verzií riadkov. Preto na začiatku obslužný proces pridelí transakcii virtuálne číslo (virtual xid). Číslo sa skladá z identifikátora procesu a poradového čísla.

Vydanie tohto čísla nevyžaduje synchronizáciu medzi všetkými procesmi a preto sa vykonáva veľmi rýchlo. S ďalším dôvodom použitia virtuálnych čísel sa zoznámime, keď budeme hovoriť o mrazení.

Virtuálne čísla sa nijako nezohľadňujú v snapshotoch údajov.

V rôznych chvíľach sa v systéme môžu nachádzať virtuálne transakcie s číslami, ktoré už boli použité, a to je normálne. Ale takéto číslo nemožno zapisovať do stránok údajov, pretože pri ďalšom prístupe k stránke môže stratiť zmysel.

=> ZAČIATOK;
=> VYBRAŤ txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                         
(1 riadok)

Ak transakcia začne meniť údaje, pridelí sa jej skutočné, jedinečné číslo transakcie.

=> AKTUALIZOVAŤ účty SET amount = amount - 1,00;
=> VYBRAŤ txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                     3667
(1 riadok)

=> COMMIT;

Vnorené transakcie

Bodové záchrany

V SQL sú definované body záchrany (savepoint), ktoré umožňujú zrušiť časť operácií transakcie bez úplného prerušenia. Ale to nezapadá do vyššie uvedenej schémy, pretože stav transakcie je jeden pre všetky jej zmeny, a fyzicky sa nijaké údaje nevracajú.

Aby sa taká funkcia realizovala, transakcia s bodom záchrany sa rozdelí na niekoľko samostatných vnorených transakcií (subtransaction), ktorých stav možno spravovať samostatne.

Vnorené transakcie majú svoje vlastné číslo (väčšie ako číslo hlavnej transakcie). Stav vnorených transakcií sa zaznamenáva bežným spôsobom v XACT, avšak konečný stav závisí od stavu hlavnej transakcie: ak je zrušená, zrušia sa aj všetky vnorené transakcie.

Informácie o vnorenosti transakcií sú uložené v súboroch v adresári PGDATA/pg_subtrans. Prístup k súborom sa uskutočňuje cez buffery v zdieľanej pamäti inštancie, usporiadané rovnako ako buffery XACT.

Nemýľte si vnorené transakcie a autonómne transakcie. Autonómne transakcie na sebe navzájom nezávisia, zatiaľ čo vnorené — závisia. Autonómne transakcie v bežnom PostgreSQL neexistujú, a možno to je lepšie: v skutočnosti sú potrebné veľmi zriedka, a ich existencia v iných DBMS vyvoláva zneužívanie, ktorého následky znášajú všetci.

Vyčistíme tabuľku, začneme transakciu a vložíme riadok:

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

Teraz nastavíme bod ukladania a vložíme ďalší riadok.

=> SAVEPOINT sp;
=> INSERT INTO t(s) VALUES ('XYZ');
=> SELECT txid_current();
 txid_current 
--------------
         3669
(1 row)

Všimnite si, že funkcia txid_current() vracia číslo hlavnej, a nie vnútorné, transakcie.

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

Vrátime sa k bodu ukladania a vložíme tretí riadok.

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

Na stránke naďalej vidíme riadok pridaný zrušenou vnútornou transakciou.

Potvrdzujeme zmeny.

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

Teraz je jasne, že každá vnútorná transakcia má svoj vlastný stav.

Spoznávame, že vnútorné transakcie nemožno v SQL použiť explicitne, teda nemožno začať novú transakciu, ak nie je aktuálna dokončená. Tento mechanizmus sa nasadzuje implicitne pri použití bodov ukladania a tiež pri spracovaní výnimiek PL/pgSQL a v mnohých iných, viac exotických, prípadoch.

=> BEGIN;
BEGIN
=> BEGIN;
UPOZORNENIE:  už prebieha transakcia
BEGIN
=> COMMIT;
COMMIT
=> COMMIT;
UPOZORNENIE:  nie je žiadna prebiehajúca transakcia
COMMIT

Chyby a atomickosť operácií

Čo sa stane, ak pri vykonávaní operácie dôjde k chybe? Napríklad takto:

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

=> UPDATE t SET s = repeat('X', 1/(id-4));
CHYBA:  delenie nulou

Došlo k chybe. Teraz sa transakcia považuje za prerušenú a žiadna operácia v nej nie je povolená:

=> SELECT * FROM t;
CHYBA:  aktuálna transakcia je prerušená, príkazy sú ignorované až do konca bloku transakcie

A aj keď sa pokúsime potvrdiť zmeny, PostgreSQL nás informuje o zrušení:

=> COMMIT;
ROLLBACK

Prečo nie je možné pokračovať v vykonávaní transakcie po zlyhaní? Ide o to, že chyba mohla nastať tak, že by sme získali prístup k časti zmien – bola by narušená atómovosť nielen transakcie, ale aj operátora. Ako v našom príklade, kde operátor stihol pred chybou aktualizovať jeden riadok:

=> SELECT * FROM heap_page('t',0);
 ctid  | stav   |   xmin   | xmax  | t_ctid 
-------+--------+----------+-------+--------
 (0,1) | normálny | 3669 (c) | 3672  | (0,4)
 (0,2) | normálny | 3670 (a) | 0 (a) | (0,2)
 (0,3) | normálny | 3671 (c) | 0 (a) | (0,3)
 (0,4) | normálny | 3672     | 0 (a) | (0,4)
(4 riadky)

Treba povedať, že v psql existuje režim, ktorý predsa len umožňuje pokračovať v práci transakcie po zlyhaní tak, ako keby sa činnosti chybných operátorov odrážali.

=> 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));
CHYBA:  delenie nulou

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

=> COMMIT;

Nie je ťažké uhádnuť, že v takom režime psql v skutočnosti umiestňuje pred každým príkazom neexplicitný bod obnovenia, a v prípade zlyhania iniciuje obnovenie k nemu. Takýto režim sa štandardne nepoužíva, pretože nastavenie bodov obnovenia (aj bez ich obnovenia) je spojené so značnými nákladmi.

Pokračovanie.

Zdroj: habr.com

Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster