MVCC-3. Strunové verze

Zvažovali jsme tedy související problémy izolacea udělali ústup organizování dat na nízké úrovni. A konečně jsme se dostali k tomu nejzajímavějšímu – k verzím strun.

Titul

Jak jsme již řekli, každý řádek může v databázi existovat současně v několika verzích. Jedna verze musí být nějak odlišena od druhé, pro tento účel má každá verze dvě značky, které určují „čas“ akce této verze (xmin a xmax). V uvozovkách – protože se nepoužívá čas jako takový, ale speciální zvyšující se počítadlo. A toto počítadlo je číslo transakce.

(Jako obvykle je realita složitější: číslo transakce nemůže neustále narůstat kvůli omezené bitové kapacitě počítadla. Na tyto detaily se ale podrobně podíváme, až se dostaneme k bodu mrazu.)

Když je vytvořen řádek, xmin je nastaveno na číslo transakce, která vydala příkaz INSERT, a xmax je ponecháno prázdné.

Po odstranění řádku je hodnota xmax aktuální verze označena číslem transakce, která provedla DELETE.

Když je řádek upraven příkazem UPDATE, jsou ve skutečnosti provedeny dvě operace: DELETE a INSERT. Aktuální verze řádku nastaví xmax na číslo transakce, která provedla UPDATE. Poté se vytvoří nová verze stejného řetězce; jeho hodnota xmin se shoduje s hodnotou xmax předchozí verze.

Pole xmin a xmax jsou zahrnuta v záhlaví verze řádku. Kromě těchto polí obsahuje záhlaví další, například:

  • infomask je řada bitů, které definují vlastnosti této verze. Je jich poměrně hodně; Postupně zvážíme ty hlavní.
  • ctid je odkaz na další, novější verzi stejného řádku. U nejnovější a nejaktuálnější verze řetězce odkazuje ctid na tuto verzi samotnou. Číslo má tvar (x,y), kde x je číslo stránky, y je číslo indexu v poli.
  • null bitmap - Označí ty sloupce dané verze, které obsahují hodnotu null (NULL). NULL nepatří mezi běžné hodnoty datového typu, takže atribut musí být uložen samostatně.

Výsledkem je, že záhlaví je poměrně velké - alespoň 23 bajtů pro každou verzi řádku a obvykle více kvůli bitmapě NULL. Pokud je tabulka "úzká" (tj. obsahuje málo sloupců), režie může zabírat více než užitečné informace.

vložit

Podívejme se blíže na to, jak se provádějí nízkoúrovňové operace s řetězci, počínaje vkládáním.

Pro experimenty vytvořte novou tabulku se dvěma sloupci a indexem v jednom z nich:

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

Po zahájení transakce vložíme jeden řádek.

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

Zde je naše aktuální číslo transakce:

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

Podívejme se na obsah stránky. Funkce heap_page_items rozšíření pageinspect vám umožňuje získat informace o ukazatelích a verzích řádků:

=> 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šimněte si, že slovo halda v PostgreSQL odkazuje na tabulky. To je další podivné použití termínu – halda je známá datová struktura, která nemá s tabulkou nic společného. Zde je slovo použito ve smyslu „všechno je shozeno dohromady“, na rozdíl od uspořádaných indexů.

Funkce zobrazuje data „tak jak jsou“ ve formátu, který je obtížně srozumitelný. Abychom na to přišli, ponecháme pouze část informací a dešifrujeme je:

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

Udělali jsme toto:

  • K číslu indexu byla přidána nula, aby vypadalo stejně jako t_ctid: (číslo stránky, číslo indexu).
  • Rozluštil stav ukazatele lp_flags. Zde je to "normální" - to znamená, že ukazatel ve skutečnosti odkazuje na verzi řetězce. Na další významy se podíváme později.
  • Ze všech informačních bitů byly zatím identifikovány pouze dva páry. Bity xmin_committed a xmin_aborted udávají, zda je číslo transakce xmin potvrzeno (přerušeno). Dva podobné bity odkazují na číslo transakce xmax.

co vidíme? Když vložíte řádek, na stránce tabulky se objeví index číslo 1, který ukazuje na první a jedinou verzi řádku.

Ve verzi řetězce je pole xmin vyplněno aktuálním číslem transakce. Transakce je stále aktivní, takže bity xmin_committed i xmin_aborted nejsou nastaveny.

Pole ctid verze řádku odkazuje na stejný řádek. To znamená, že novější verze neexistuje.

Pole xmax je vyplněno fiktivním číslem 0, protože tato verze řádku nebyla smazána a je aktuální. Transakce nebudou tomuto číslu věnovat pozornost, protože je nastaven bit xmax_aborted.

Udělejme ještě jeden krok ke zlepšení čitelnosti přidáním informačních bitů k číslům transakcí. A vytvoříme funkci, protože požadavek budeme potřebovat více než jednou:

=> 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 této podobě je mnohem jasnější, co se děje v záhlaví verze řádku:

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

Podobné, ale podstatně méně podrobné informace lze získat ze samotné tabulky pomocí pseudosloupců xmin a xmax:

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

Fixace

Pokud je transakce úspěšně dokončena, musíte si zapamatovat její stav – všimněte si, že je potvrzena. K tomu se používá struktura nazvaná XACT (a před verzí 10 se jmenovala CLOG (commit log) a tento název lze stále najít na různých místech).

XACT není tabulka systémového katalogu; toto jsou soubory v adresáři PGDATA/pg_xact. Mají dva bity pro každou transakci: potvrzenou a přerušenou – stejně jako v záhlaví verze řádku. Tyto informace jsou pouze pro pohodlí rozděleny do několika souborů; k tomuto problému se vrátíme, až zvážíme zmrazení. A práce s těmito soubory se provádí stránku po stránce, jako se všemi ostatními.

Takže když je transakce potvrzena v XACT, je pro tuto transakci nastaven potvrzený bit. A to je vše, co se děje během provádění (ačkoli ještě nemluvíme o protokolu před nahráváním).

Když na stránku tabulky, na kterou jsme se právě dívali, přistoupí jiná transakce, bude muset odpovědět na několik otázek.

  1. Byla transakce xmin dokončena? Pokud ne, pak by vytvořená verze řetězce neměla být viditelná.
    Tato kontrola se provádí pohledem na jinou strukturu, která se nachází ve sdílené paměti instance a nazývá se ProcArray. Obsahuje seznam všech aktivních procesů a u každého je uvedeno číslo jeho aktuální (aktivní) transakce.
  2. Je-li dokončeno, jak – zavázáním nebo zrušením? Pokud je zrušeno, neměla by být viditelná ani verze řádku.
    Přesně k tomu slouží XACT. Ale ačkoli jsou poslední stránky XACT uloženy ve vyrovnávací paměti v RAM, je stále drahé kontrolovat XACT pokaždé. Jakmile je tedy stav transakce určen, je zapsán do bitů xmin_committed a xmin_aborted verze řetězce. Pokud je nastaven jeden z těchto bitů, pak se stav transakce xmin považuje za známý a další transakce nebude muset přistupovat k XACT.

Proč nejsou tyto bity nastaveny samotnou transakcí, která provádí vkládání? Když dojde k vložení, transakce ještě neví, zda bude úspěšná. A v okamžiku potvrzení již není jasné, které řádky byly změněny. Takových stránek může být hodně a jejich zapamatování je nerentabilní. Kromě toho mohou být některé stránky vyřazeny z vyrovnávací paměti na disk; jejich opětovné přečtení za účelem změny bitů by výrazně zpomalilo odevzdání.

Nevýhodou úspor je, že po změnách může jakákoli transakce (i ta provádějící jednoduché čtení - SELECT) začít měnit datové stránky ve vyrovnávací paměti.

Pojďme tedy změnu napravit.

=> COMMIT;

Na stránce se nic nezměnilo (ale víme, že stav transakce je již zaznamenán v XACT):

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

Nyní bude muset transakce, která přistupuje na stránku jako první, určit stav transakce xmin a zapsat jej do informačních bitů:

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

Odstranění

Po odstranění řádku se do pole xmax aktuální verze zapíše číslo aktuální transakce odstranění a bit xmax_aborted se vymaže.

Všimněte si, že nastavená hodnota xmax odpovídající aktivní transakci funguje jako zámek řádku. Pokud bude chtít jiná transakce aktualizovat nebo odstranit tento řádek, bude nucena počkat na dokončení transakce xmax. O blokování si povíme více později. Prozatím jen poznamenáváme, že počet zámků řádků je neomezený. Nezabírají místo v RAM a jejich počtem netrpí ani výkon systému. Pravda, „dlouhé“ transakce mají další nevýhody, ale o tom později.

Smažeme řádek.

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

Vidíme, že číslo transakce je zapsáno do pole xmax, ale nejsou nastaveny informační bity:

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

Zrušení

Aborting změn funguje podobně jako commiting, pouze v XACT je pro transakci nastaven bit aborted. Zrušení je stejně rychlé jako zavázání. Přestože se příkaz nazývá ROLLBACK, změny nejsou vráceny zpět: vše, co se transakci podařilo změnit na datových stránkách, zůstává nezměněno.

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

Při přístupu na stránku bude zkontrolován stav a bit nápovědy xmax_aborted bude nastaven na verzi řádku. Samotné číslo xmax na stránce zůstává, ale nikdo se na něj nepodívá.

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

Aktualizovat

Aktualizace funguje tak, že nejprve smaže aktuální verzi řádku a poté vloží novou.

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

Dotaz vytvoří jeden řádek (nová verze):

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

Ale na stránce vidíme obě verze:

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

Smazaná verze je označena aktuálním číslem transakce v poli xmax. Navíc se tato hodnota přepíše přes starou, protože předchozí transakce byla zrušena. A bit xmax_aborted se vymaže, protože stav aktuální transakce ještě není znám.

První verze řádku nyní odkazuje na druhou (pole t_ctid) jako na novější.

Druhý rejstřík se zobrazí na stránce rejstříku a druhý řádek odkazuje na druhou verzi na stránce tabulky.

Stejně jako u mazání je hodnota xmax v první verzi řádku indikací, že řádek je uzamčen.

No, dokončíme transakci.

=> COMMIT;

Indexy

Zatím jsme mluvili pouze o tabulkových stránkách. Co se děje uvnitř indexů?

Informace na stránkách rejstříku se značně liší v závislosti na konkrétním typu rejstříku. A dokonce i jeden typ indexu má různé typy stránek. Například B-strom má stránku metadat a „běžné“ stránky.

Stránka však obvykle obsahuje pole ukazatelů na řádky a řádky samotné (stejně jako stránka tabulky). Na konci stránky je navíc prostor pro speciální údaje.

Řádky v indexech mohou mít také velmi odlišné struktury v závislosti na typu indexu. Například pro B-strom obsahují řádky související s listovými stránkami hodnotu indexovacího klíče a odkaz (ctid) na odpovídající řádek tabulky. Obecně může být index strukturován zcela jiným způsobem.

Nejdůležitější je, že v indexech jakéhokoli typu neexistují verze řádků. No, nebo můžeme předpokládat, že každý řádek je reprezentován právě jednou verzí. Jinými slovy, v záhlaví řádku indexu nejsou žádná pole xmin a xmax. Můžeme předpokládat, že odkazy z indexu vedou na všechny tabulkové verze řádků – takže můžete zjistit, kterou verzi transakce uvidí, pouze pohledem do tabulky. (Jako vždy to není celá pravda. V některých případech může mapa viditelnosti proces optimalizovat, ale na to se podíváme podrobněji později.)

Zároveň na stránce indexu najdeme ukazatele na obě verze, jak na aktuální, tak na starou:

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

Virtuální transakce

V praxi PostgreSQL používá optimalizace, které mu umožňují „ukládat“ čísla transakcí.

Pokud transakce pouze čte data, nemá to žádný vliv na viditelnost verzí řádků. Proto proces služby nejprve vydá virtuální xid k transakci. Číslo se skládá z ID procesu a pořadového čísla.

Vydání tohoto čísla nevyžaduje synchronizaci mezi všemi procesy a je tedy velmi rychlé. S dalším důvodem používání virtuálních čísel se seznámíme, když mluvíme o zmrazení.

Virtuální čísla nejsou v datových snímcích nijak zohledněna.

V různých okamžicích mohou v systému probíhat virtuální transakce s čísly, která již byla použita, což je normální. Takové číslo však nelze zapsat do datových stránek, protože při příštím přístupu na stránku může ztratit veškerý význam.

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

Pokud transakce začne měnit data, je jí přiděleno skutečné, jedinečné číslo transakce.

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

=> COMMIT;

Vnořené transakce

Uložit body

Definováno v SQL uložit body (savepoint), které umožňují zrušit část transakce bez jejího úplného přerušení. To však nezapadá do výše uvedeného diagramu, protože transakce má stejný stav pro všechny změny a fyzicky nejsou vrácena žádná data.

Pro implementaci této funkce je transakce s bodem uložení rozdělena na několik samostatných vnořené transakce (subtransakce), jejíž stav lze spravovat samostatně.

Vnořené transakce mají své vlastní číslo (vyšší než číslo hlavní transakce). Stav vnořených transakcí se zaznamenává obvyklým způsobem v XACT, ale konečný stav závisí na stavu hlavní transakce: pokud je zrušena, jsou zrušeny i všechny vnořené transakce.

Informace o vnoření transakcí jsou uloženy v souborech v adresáři PGDATA/pg_subtrans. K souborům se přistupuje prostřednictvím vyrovnávacích pamětí ve sdílené paměti instance, které jsou organizovány stejným způsobem jako vyrovnávací paměti XACT.

Nepleťte si vnořené transakce s autonomními transakcemi. Autonomní transakce na sobě nijak nezávisí, ale vnořené transakce ano. V běžném PostgreSQL neexistují žádné autonomní transakce, a možná v tom nejlepším případě: jsou potřeba velmi, velmi zřídka a jejich přítomnost v jiných DBMS vyvolává zneužívání, kterým pak trpí každý.

Vyčistíme tabulku, zahájíme transakci a vložíme řádek:

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

Nyní dáme bod uložení a vložíme další řádek.

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

Všimněte si, že funkce txid_current() vrací hlavní číslo transakce, nikoli vnořené číslo transakce.

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

Vraťme se zpět k bodu uložení a vložíme třetí řádek.

=> 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ánce nadále vidíme řádek přidaný zrušenou vnořenou transakcí.

Opravujeme změny.

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

Nyní můžete jasně vidět, že každá vnořená transakce má svůj vlastní stav.

Všimněte si, že vnořené transakce nelze použít explicitně v SQL, to znamená, že nemůžete zahájit novou transakci, aniž byste dokončili tu aktuální. Tento mechanismus se aktivuje implicitně při použití záchranných bodů, stejně jako při zpracování výjimek PL/pgSQL a v řadě dalších, exotičtějších případů.

=> 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 atomičnost operací

Co se stane, když při provádění operace dojde k chybě? Napří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 chybě. Nyní je transakce považována za přerušenou a nejsou v ní povoleny žádné operace:

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

A i když se pokusíte provést změny, PostgreSQL oznámí přerušení:

=> COMMIT;
ROLLBACK

Proč nemůže transakce po selhání pokračovat? Chyba by totiž mohla vzniknout tak, že bychom získali přístup k části změn - byla by narušena atomičnost ani ne transakce, ale operátora. Jako v našem příkladu, kde se operátorovi podařilo aktualizovat jeden řádek před 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 třeba říci, že psql má režim, který stále umožňuje, aby transakce po selhání pokračovala, jako by byly akce chybného operátora vráceny zpět.

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

Není těžké uhodnout, že v tomto režimu psql ve skutečnosti umístí implicitní bod uložení před každý příkaz a v případě selhání zahájí návrat k němu. Tento režim se ve výchozím nastavení nepoužívá, protože nastavení bodů uložení (i bez návratu k nim) vyžaduje značnou režii.

Pokračovat.

Zdroj: www.habr.com

Přidat komentář