MVCC-3. Strunové verzie

Takže sme zvážili problémy súvisiace s izolácia, a urobil ústup okolo organizovanie údajov na nízkej úrovni. A konečne sme sa dostali k tomu najzaujímavejšiemu – strunové verzie.

Titul

Ako sme už povedali, každý riadok môže súčasne existovať v niekoľkých verziách v databáze. Jedna verzia musí byť nejako odlíšená od druhej, na tento účel má každá verzia dve značky, ktoré určujú „čas“ pôsobenia tejto verzie (xmin a xmax). V úvodzovkách – pretože sa nepoužíva čas ako taký, ale špeciálne zvyšujúce sa počítadlo. A toto počítadlo je číslo transakcie.

(Ako to už býva, realita je zložitejšia: číslo transakcie nemôže neustále narastať kvôli obmedzenej bitovej kapacite počítadla. Na tieto detaily sa však podrobne pozrieme, keď sa dostaneme k zmrazeniu.)

Keď sa vytvorí riadok, xmin sa nastaví na číslo transakcie, ktorá vydala príkaz INSERT, a xmax zostane prázdne.

Po odstránení riadka sa hodnota xmax aktuálnej verzie označí číslom transakcie, ktorá vykonala DELETE.

Keď je riadok upravený príkazom UPDATE, v skutočnosti sa vykonajú dve operácie: DELETE a INSERT. Aktuálna verzia riadku nastaví xmax na číslo, ktoré sa rovná číslu transakcie, ktorá vykonala UPDATE. Potom sa vytvorí nová verzia toho istého reťazca; jeho hodnota xmin sa zhoduje s hodnotou xmax predchádzajúcej verzie.

Polia xmin a xmax sú zahrnuté v hlavičke verzie riadka. Okrem týchto polí hlavička obsahuje ďalšie, napríklad:

  • infomask je séria bitov, ktoré definujú vlastnosti tejto verzie. Je ich pomerne veľa; Postupne zvážime tie hlavné.
  • ctid je odkaz na ďalšiu, novšiu verziu toho istého riadku. Pre najnovšiu a najaktuálnejšiu verziu reťazca sa ctid vzťahuje na samotnú verziu. Číslo má tvar (x,y), kde x je číslo stránky, y je číslo indexu v poli.
  • null bitmap - Označí tie stĺpce danej verzie, ktoré obsahujú hodnotu null (NULL). NULL nie je jednou z normálnych hodnôt typu údajov, takže atribút musí byť uložený oddelene.

Výsledkom je, že hlavička je pomerne veľká - najmenej 23 bajtov pre každú verziu riadku a zvyčajne viac kvôli bitovej mape NULL. Ak je tabuľka „úzka“ (to znamená, že obsahuje málo stĺpcov), réžia môže zaberať viac ako užitočné informácie.

vložiť

Pozrime sa bližšie na to, ako sa vykonávajú operácie s reťazcami na nízkej úrovni, počnúc vkladaním.

Pre experimenty vytvorte novú tabuľku s dvoma stĺpcami a indexom v jednom z nich:

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

Po spustení transakcie vložíme jeden riadok.

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

Tu je naše aktuálne číslo transakcie:

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

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

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

Všimnite si, že slovo halda v PostgreSQL odkazuje na tabuľky. Toto je ďalšie zvláštne použitie termínu - halda je známa dátová štruktúra, ktorá nemá s tabuľkou nič spoločné. Toto slovo sa tu používa v zmysle „všetko je zhodené“, na rozdiel od usporiadaných indexov.

Funkcia zobrazuje údaje „tak, ako sú“, vo formáte, ktorý je ťažko zrozumiteľný. Aby sme na to prišli, ponecháme iba časť informácií a rozlúštime ich:

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

Tu je to, čo sme urobili:

  • Do indexového čísla bola pridaná nula, aby vyzeralo rovnako ako t_ctid: (číslo strany, indexové číslo).
  • Dešifroval stav ukazovateľa lp_flags. Tu je to "normálne" - to znamená, že ukazovateľ skutočne odkazuje na verziu reťazca. Na ďalšie významy sa pozrieme neskôr.
  • Zo všetkých informačných bitov boli zatiaľ identifikované iba dva páry. Bity xmin_committed a xmin_aborted označujú, či je číslo transakcie xmin potvrdené (prerušené). Dva podobné bity odkazujú na číslo transakcie xmax.

čo vidíme? Keď vložíte riadok, na stránke tabuľky sa zobrazí číslo indexu 1, ktoré ukazuje na prvú a jedinú verziu riadka.

Vo verzii reťazca je pole xmin vyplnené aktuálnym číslom transakcie. Transakcia je stále aktívna, takže bity xmin_committed aj xmin_aborted nie sú nastavené.

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

Pole xmax je vyplnené fiktívnym číslom 0, pretože táto verzia riadka nebola vymazaná a je aktuálna. Transakcie nebudú venovať pozornosť tomuto číslu, pretože je nastavený bit xmax_aborted.

Urobme ešte jeden krok smerom k zlepšeniu čitateľnosti pridaním informačných bitov k číslam transakcií. A vytvoríme funkciu, pretože požiadavku budeme potrebovať viackrát:

=> 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 oveľa 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 row)

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

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

fixácia

Ak je transakcia úspešne dokončená, musíte si zapamätať jej stav – všimnite si, že je potvrdená. Na tento účel sa používa štruktúra s názvom XACT (a pred verziou 10 sa nazývala CLOG (commit log) a tento názov možno stále nájsť na rôznych miestach).

XACT nie je tabuľka systémového katalógu; toto sú súbory v adresári PGDATA/pg_xact. Majú dva bity pre každú transakciu: potvrdenú a prerušenú – rovnako ako v hlavičke verzie riadka. Tieto informácie sú rozdelené do niekoľkých súborov len pre pohodlie; k tomuto problému sa vrátime, keď zvážime zmrazenie. A práca s týmito súbormi sa vykonáva stránku po stránke, ako so všetkými ostatnými.

Takže keď je transakcia potvrdená v XACT, potvrdený bit je nastavený pre túto transakciu. A to je všetko, čo sa deje počas odovzdávania (hoci ešte nehovoríme o protokole pred nahrávaním).

Keď iná transakcia pristúpi na stránku tabuľky, na ktorú sme sa práve pozreli, bude musieť odpovedať na niekoľko otázok.

  1. Bola transakcia xmin dokončená? Ak nie, potom by vytvorená verzia reťazca nemala byť viditeľná.
    Táto kontrola sa vykonáva pohľadom na inú štruktúru, ktorá sa nachádza v zdieľanej pamäti inštancie a nazýva sa ProcArray. Obsahuje zoznam všetkých aktívnych procesov a pri každom je uvedené číslo jeho aktuálnej (aktívnej) transakcie.
  2. Ak je dokončená, ako potom - zaviazaním alebo zrušením? Ak sa zruší, potom by nemala byť viditeľná ani verzia riadka.
    Presne na to slúži XACT. Ale aj keď sú posledné stránky XACT uložené vo vyrovnávacej pamäti v RAM, je stále drahé kontrolovať XACT zakaždým. Preto, keď sa zistí stav transakcie, zapíše sa do bitov xmin_committed a xmin_aborted verzie reťazca. Ak je nastavený jeden z týchto bitov, potom sa stav transakcie xmin považuje za známy a ďalšia transakcia nebude musieť pristupovať k XACT.

Prečo nie sú tieto bity nastavené samotnou transakciou, ktorá vykonáva vkladanie? Keď dôjde k vloženiu, transakcia ešte nevie, či bude úspešná. A v momente potvrdenia už nie je jasné, v ktorých riadkoch boli zmenené strany. Takýchto stránok môže byť veľa a ich zapamätanie je nerentabilné. Okrem toho môžu byť niektoré stránky vyradené z vyrovnávacej pamäte na disk; ich opätovné prečítanie na zmenu bitov by výrazne spomalilo odovzdanie.

Nevýhodou úspor je, že po zmenách môže akákoľvek transakcia (aj tá vykonávajúca jednoduché čítanie – SELECT) začať meniť dátové stránky vo vyrovnávacej pamäti.

Tak poďme napraviť 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 row)

Teraz bude musieť transakcia, ktorá pristupuje na stránku ako prvá, určiť stav transakcie xmin a zapísať 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

Keď sa vymaže riadok, číslo aktuálnej transakcie vymazania sa zapíše do poľa xmax aktuálnej verzie a bit xmax_aborted sa vymaže.

Všimnite si, že nastavená hodnota xmax zodpovedajúca aktívnej transakcii funguje ako zámok riadku. Ak chce iná transakcia aktualizovať alebo vymazať tento riadok, bude nútená počkať na dokončenie transakcie xmax. O blokovaní si povieme viac neskôr. Zatiaľ si všimneme, že počet zámkov riadkov je neobmedzený. Nezaberajú miesto v RAM a ich počtom neutrpí ani výkon systému. Je pravda, že „dlhé“ transakcie majú ďalšie nevýhody, ale o tom neskôr.

Vymažeme riadok.

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

Vidíme, že číslo transakcie je zapísané v poli xmax, ale informačné bity nie sú nastavené:

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

Zrušenie

Aborting zmien funguje podobne ako commit, len v XACT je pre transakciu nastavený bit prerušenia. Zrušenie je také rýchle ako zaviazanie. Hoci sa príkaz nazýva ROLLBACK, zmeny sa nevrátia späť: všetko, čo sa transakcii podarilo zmeniť na dátových stránkach, zostáva nezmenené.

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

Pri prístupe na stránku sa skontroluje stav a xmax_aborted hint bit sa nastaví na verziu riadka. Samotné číslo xmax zostáva na stránke, ale nikto sa naň nepozrie.

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

Aktualizovať

Aktualizácia funguje tak, ako keby najprv vymazala aktuálnu verziu riadku a potom vložila novú.

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

Dotaz vytvorí jeden riadok (nová verzia):

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

Na stránke však vidíme obe verzie:

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

Odstránená verzia je označená aktuálnym číslom transakcie v poli xmax. Okrem toho sa táto hodnota prepíše cez starú, pretože predchádzajúca transakcia bola zrušená. A bit xmax_aborted sa vymaže, pretože stav aktuálnej transakcie ešte nie je známy.

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

Druhý index sa zobrazí na stránke indexu a druhý riadok odkazuje na druhú verziu na stránke tabuľky.

Rovnako ako pri odstraňovaní, hodnota xmax v prvej verzii riadka označuje, že riadok je uzamknutý.

No, dokončite transakciu.

=> COMMIT;

Indexy

Doteraz sme hovorili len o tabuľkových stranách. Čo sa deje vo vnútri indexov?

Informácie na stránkach indexu sa značne líšia v závislosti od konkrétneho typu indexu. A dokonca aj jeden typ indexu má rôzne typy stránok. Napríklad B-strom má stránku metadát a „bežné“ stránky.

Stránka však zvyčajne obsahuje pole ukazovateľov na riadky a samotné riadky (rovnako ako stránka tabuľky). Okrem toho je na konci stránky priestor 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 obsahujú riadky súvisiace s listovými stránkami hodnotu indexovacieho kľúča a odkaz (ctid) na zodpovedajúci riadok tabuľky. Vo všeobecnosti môže byť index štruktúrovaný úplne iným spôsobom.

Najdôležitejším bodom je, že v indexoch akéhokoľvek typu neexistujú verzie riadkov. Alebo môžeme predpokladať, že každý riadok je reprezentovaný práve jednou verziou. Inými slovami, v hlavičke riadka indexu nie sú žiadne polia xmin a xmax. Môžeme predpokladať, že odkazy z indexu vedú do všetkých verzií tabuliek riadkov – takže len pri pohľade na tabuľku môžete zistiť, ktorú verziu transakcia uvidí. (Ako vždy, toto nie je celá pravda. V niektorých prípadoch môže mapa viditeľnosti optimalizovať proces, ale na to sa podrobnejšie pozrieme neskôr.)

Zároveň na stránke indexu nájdeme ukazovatele na obe verzie, aktuálnu aj starú:

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

Virtuálne transakcie

V praxi PostgreSQL využíva optimalizácie, ktoré mu umožňujú „ukladať“ čísla transakcií.

Ak transakcia iba číta údaje, nemá to žiadny vplyv na viditeľnosť verzií riadkov. Preto proces služby najprv vydá virtuálny xid transakcii. Číslo pozostáva z ID procesu a poradového čísla.

Vydanie tohto čísla nevyžaduje synchronizáciu medzi všetkými procesmi a je teda veľmi rýchle. S ďalším dôvodom používania virtuálnych čísel sa zoznámime, keď hovoríme o zmrazení.

Virtuálne čísla sa pri snímkach údajov žiadnym spôsobom nezohľadňujú.

V rôznych časových okamihoch môžu byť v systéme virtuálne transakcie s číslami, ktoré už boli použité, čo je normálne. Takéto číslo však nemožno zapísať do dátových stránok, pretože pri ďalšom prístupe na stránku môže stratiť akýkoľvek význam.

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

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

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

=> COMMIT;

Vnorené transakcie

Uložiť body

Definované v SQL uložiť body (savepoint), ktoré vám umožňujú zrušiť časť transakcie bez jej úplného prerušenia. To však nezapadá do vyššie uvedeného diagramu, pretože transakcia má rovnaký stav pre všetky zmeny a fyzicky sa žiadne údaje nevracajú späť.

Na implementáciu tejto funkcie je transakcia s bodom uloženia rozdelená na niekoľko samostatných vnorené transakcie (subtransakcia), ktorej stav je možné spravovať samostatne.

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

Informácie o vnorení transakcií sú uložené v súboroch v adresári PGDATA/pg_subtrans. K súborom sa pristupuje cez vyrovnávacie pamäte v zdieľanej pamäti inštancie, ktoré sú organizované rovnakým spôsobom ako vyrovnávacie pamäte XACT.

Nezamieňajte vnorené transakcie s autonómnymi transakciami. Autonómne transakcie na sebe nijako nezávisia, ale vnorené transakcie áno. V bežnom PostgreSQL neexistujú žiadne autonómne transakcie a možno v tom najlepšom prípade: sú potrebné veľmi, veľmi zriedkavo a ich prítomnosť v iných DBMS vyvoláva zneužívanie, ktorým potom trpí každý.

Vyčistime tabuľku, začnime 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 dáme bod uloženia 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 hlavné číslo transakcie, nie vnorené číslo 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áťme sa späť k bodu uloženia 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 vnorenou transakciou.

Opravujeme 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 môžete jasne vidieť, že každá vnorená transakcia má svoj vlastný stav.

Všimnite si, že vnorené transakcie nemožno použiť explicitne v SQL, to znamená, že nemôžete začať novú transakciu bez dokončenia aktuálnej. Tento mechanizmus sa aktivuje implicitne pri použití záchranných bodov, ako aj pri manipulácii s výnimkami PL/pgSQL a v mnohých ďalších exotickejších prípadoch.

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

Chyby a atomicita operácií

Čo sa stane, ak sa pri vykonávaní operácie vyskytne chyba? Napríklad takto:

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

Došlo k chybe. Teraz sa transakcia považuje za prerušenú a nie sú v nej povolené žiadne operácie:

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

A aj keď sa pokúsite potvrdiť zmeny, PostgreSQL ohlási prerušenie:

=> COMMIT;
ROLLBACK

Prečo po zlyhaní nemôže transakcia pokračovať? Chyba by totiž mohla vzniknúť tak, že by sme získali prístup k časti zmien - atomicita ani nie transakcie, ale došlo by k porušeniu operátora. Ako v našom príklade, kde sa operátorovi podarilo aktualizovať jeden riadok pred chybou:

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

Je potrebné povedať, že psql má režim, ktorý stále umožňuje pokračovať v transakcii po zlyhaní, ako keby boli akcie chybného operátora vrátené späť.

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

Nie je ťažké uhádnuť, že v tomto režime psql v skutočnosti pred každým príkazom vloží implicitný bod uloženia a v prípade zlyhania spustí návrat k nemu. Tento režim sa štandardne nepoužíva, pretože nastavenie bodov uloženia (dokonca aj bez návratu k nim) si vyžaduje značnú réžiu.

Pokračovanie.

Zdroj: hab.com

Pridať komentár