MVCC-3. Nizke različice

Torej smo obravnavali vprašanja, povezana z izolacija, in se umaknil približno organiziranje podatkov na nizki ravni. In končno smo prišli do najzanimivejšega dela – godalnih različic.

Naslov

Kot smo že povedali, lahko vsaka vrstica hkrati obstaja v več različicah v bazi podatkov. Eno različico je treba nekako ločiti od druge, zato ima vsaka različica dve oznaki, ki določata »čas« delovanja te različice (xmin in xmax). V narekovajih - ker se ne uporablja čas kot tak, ampak poseben naraščajoči števec. In ta števec je številka transakcije.

(Kot običajno je realnost bolj zapletena: številka transakcije se ne more ves čas povečevati zaradi omejene bitne zmogljivosti števca. Toda te podrobnosti si bomo podrobneje ogledali, ko pridemo do zamrznitve.)

Ko je vrstica ustvarjena, je xmin nastavljen na številko transakcije, ki je izdala ukaz INSERT, xmax pa ostane prazen.

Ko je vrstica izbrisana, je vrednost xmax trenutne različice označena s številko transakcije, ki je izvedla DELETE.

Ko je vrstica spremenjena z ukazom UPDATE, se dejansko izvedeta dve operaciji: DELETE in INSERT. Trenutna različica vrstice nastavi xmax enako številu transakcije, ki je izvedla POSODOBITEV. Nato se ustvari nova različica istega niza; njegova vrednost xmin sovpada z vrednostjo xmax prejšnje različice.

Polji xmin in xmax sta vključeni v glavo različice vrstice. Poleg teh polj vsebuje glava še druga, na primer:

  • infomaska ​​je serija bitov, ki določajo lastnosti te različice. Kar veliko jih je; Postopoma bomo upoštevali glavne.
  • ctid je povezava do naslednje, novejše različice iste vrstice. Za najnovejšo, najnovejšo različico niza se ctid nanaša na samo to različico. Število ima obliko (x,y), kjer je x številka strani, y je številka indeksa v matriki.
  • ničelna bitna slika - Označuje tiste stolpce dane različice, ki vsebujejo ničelno vrednost (NULL). NULL ni ena od običajnih vrednosti podatkovnega tipa, zato je treba atribut shraniti ločeno.

Posledično je glava precej velika - vsaj 23 bajtov za vsako različico vrstice in običajno več zaradi bitne slike NULL. Če je tabela "ozka" (to pomeni, da vsebuje malo stolpcev), lahko režijski stroški zavzamejo več kot koristne informacije.

vstavite

Oglejmo si podrobneje, kako se izvajajo operacije z nizi na nizki ravni, začenši z vstavljanjem.

Za poskuse ustvarimo novo tabelo z dvema stolpcema in indeksom enega od njiju:

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

Vstavimo eno vrstico po začetku transakcije.

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

Tukaj je naša trenutna številka transakcije:

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

Poglejmo si vsebino strani. Funkcija heap_page_items razširitve pageinspect omogoča pridobivanje informacij o kazalcih in različicah vrstic:

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

Upoštevajte, da se beseda kup v PostgreSQL nanaša na tabele. To je še ena čudna uporaba izraza - kopica je znana struktura podatkov, ki nima nič skupnega z mizo. Tukaj je beseda uporabljena v pomenu "vse je vrženo skupaj", v nasprotju z urejenimi indeksi.

Funkcija prikazuje podatke »takšne kot so«, v obliki, ki jo je težko razumeti. Da bi to ugotovili, bomo pustili le del informacij in jih dešifrirali:

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

Evo, kaj smo storili:

  • Številki indeksa dodana ničla, da je videti enako kot t_ctid: (številka strani, številka indeksa).
  • Dešifrirano stanje kazalca lp_flags. Tukaj je "normalno" - to pomeni, da se kazalec dejansko nanaša na različico niza. Kasneje si bomo ogledali druge pomene.
  • Od vseh informacijskih bitov sta bila doslej identificirana samo dva para. Bita xmin_committed in xmin_aborted označujeta, ali je številka transakcije xmin odobrena (prekinjena). Dva podobna bita se nanašata na številko transakcije xmax.

Kaj vidimo? Ko vstavite vrstico, se bo na strani tabele pojavila indeksna številka 1, ki kaže na prvo in edino različico vrstice.

V nizovni različici se polje xmin izpolni s trenutno številko transakcije. Transakcija je še vedno aktivna, zato bita xmin_committed in xmin_aborted nista nastavljena.

Polje ctid različice vrstice se nanaša na isto vrstico. To pomeni, da novejša različica ne obstaja.

Polje xmax je izpolnjeno z navidezno številko 0, ker ta različica vrstice ni bila izbrisana in je trenutna. Transakcije ne bodo upoštevale te številke, ker je nastavljen bit xmax_aborted.

Naredimo še en korak k izboljšanju berljivosti z dodajanjem informacijskih bitov številkam transakcij. In ustvarimo funkcijo, saj bomo zahtevo potrebovali več kot enkrat:

=> 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 tej obliki je veliko bolj jasno, kaj se dogaja v glavi različice vrstice:

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

Podobne, vendar bistveno manj podrobne informacije je mogoče pridobiti iz same tabele z uporabo psevdostolpcev xmin in xmax:

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

Pritrditev

Če je transakcija uspešno zaključena, si morate zapomniti njen status – upoštevajte, da je zavezana. Za to se uporablja struktura, imenovana XACT (in pred različico 10 se je imenovala CLOG (dnevnik odobritev) in to ime je še vedno mogoče najti na različnih mestih).

XACT ni tabela sistemskega kataloga; to so datoteke v imeniku PGDATA/pg_xact. Imajo dva bita za vsako transakcijo: committed in aborted - tako kot v glavi različice vrstice. Te informacije so razdeljene v več datotek izključno zaradi priročnosti; k tej temi se bomo vrnili, ko bomo razmišljali o zamrznitvi. In delo s temi datotekami poteka stran za stranjo, tako kot z vsemi drugimi.

Torej, ko je transakcija odobrena v XACT, je odobreni bit nastavljen za to transakcijo. In to je vse, kar se zgodi med predajo (čeprav še ne govorimo o dnevniku pred snemanjem).

Ko druga transakcija dostopi do strani tabele, ki smo si jo pravkar ogledali, bo morala odgovoriti na več vprašanj.

  1. Ali je transakcija xmin zaključena? Če ni, ustvarjena različica niza ne bi smela biti vidna.
    To preverjanje se izvede z ogledom druge strukture, ki se nahaja v skupnem pomnilniku instance in se imenuje ProcArray. Vsebuje seznam vseh aktivnih procesov, za vsakega pa je navedena številka trenutne (aktivne) transakcije.
  2. Če je izpolnjeno, kako potem - z zavezo ali preklicem? Če je preklican, tudi različica vrstice ne bi smela biti vidna.
    Točno temu je namenjen XACT. Toda čeprav so zadnje strani XACT shranjene v medpomnilnikih v RAM-u, je vsakokratno preverjanje XACT še vedno drago. Ko je status transakcije določen, se torej zapiše v bita xmin_committed in xmin_aborted različice niza. Če je eden od teh bitov nastavljen, se stanje transakcije xmin šteje za znano in naslednji transakciji ne bo treba dostopati do XACT.

Zakaj teh bitov ne nastavi sama transakcija, ki izvaja vstavljanje? Ko pride do vstavka, transakcija še ne ve, ali bo uspela. In v trenutku izvršitve ni več jasno, katere vrstice, v katerih straneh so bile spremenjene. Takih strani je lahko veliko in njihovo pomnjenje je nedonosno. Poleg tega je mogoče nekatere strani odstraniti iz medpomnilnika na disk; njihovo ponovno branje za spremembo bitov bi znatno upočasnilo objavo.

Slaba stran prihrankov je, da lahko po spremembah katera koli transakcija (tudi tista, ki izvaja preprosto branje - SELECT) začne spreminjati podatkovne strani v medpomnilniku.

Torej, popravimo spremembo.

=> COMMIT;

Na strani se ni nič spremenilo (vemo pa, da je stanje transakcije že zabeleženo v XACT):

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

Zdaj bo morala transakcija, ki prva dostopa do strani, določiti status transakcije xmin in ga zapisati v informacijske bite:

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

Brisanje

Ko je vrstica izbrisana, se številka trenutne transakcije za brisanje zapiše v polje xmax trenutne različice, bit xmax_aborted pa se počisti.

Upoštevajte, da nastavljena vrednost xmax, ki ustreza aktivni transakciji, deluje kot zaklepanje vrstice. Če želi druga transakcija posodobiti ali izbrisati to vrstico, bo prisiljena počakati, da se transakcija xmax zaključi. O blokadi bomo več govorili kasneje. Zaenkrat upoštevamo le, da je število zaklepanja vrstic neomejeno. Ne zasedajo prostora v RAM-u in njihovo število ne vpliva na delovanje sistema. Res je, da imajo "dolge" transakcije še druge slabosti, a o tem kasneje.

Zbrišemo vrstico.

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

Vidimo, da je številka transakcije zapisana v polju xmax, vendar informacijski biti niso nastavljeni:

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

Preklic

Prekinitev sprememb deluje podobno kot potrditev, le da je v XACT za transakcijo nastavljen prekinjeni bit. Razveljavitev je tako hitra kot zaveza. Čeprav se ukaz imenuje ROLLBACK, se spremembe ne povrnejo nazaj: vse, kar je transakcija uspela spremeniti na podatkovnih straneh, ostane nespremenjeno.

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

Ko je stran dostopna, bo status preverjen in bit namiga xmax_aborted nastavljen na različico vrstice. Sama številka xmax ostane na strani, vendar je nihče ne pogleda.

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

Posodobiti

Posodobitev deluje, kot da bi najprej izbrisala trenutno različico vrstice in nato vstavila novo.

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

Poizvedba ustvari eno vrstico (nova različica):

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

Toda na strani vidimo obe različici:

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

Izbrisana različica je označena s trenutno številko transakcije v polju xmax. Poleg tega je ta vrednost zapisana čez staro, saj je bila prejšnja transakcija preklicana. In bit xmax_aborted je počiščen, ker status trenutne transakcije še ni znan.

Prva različica vrstice se zdaj nanaša na drugo (polje t_ctid) kot novejšo.

Drugo kazalo se pojavi na indeksni strani, druga vrstica pa se sklicuje na drugo različico na strani tabele.

Tako kot pri brisanju je vrednost xmax v prvi različici vrstice znak, da je vrstica zaklenjena.

No, zaključimo transakcijo.

=> COMMIT;

Indeksi

Do sedaj smo govorili le o straneh tabel. Kaj se dogaja znotraj indeksov?

Informacije na straneh indeksa se zelo razlikujejo glede na določeno vrsto indeksa. In celo ena vrsta kazala ima različne vrste strani. B-drevo ima na primer stran z metapodatki in »navadne« strani.

Vendar pa ima stran običajno niz kazalcev na vrstice in same vrstice (tako kot stran tabele). Poleg tega je na koncu strani prostor za posebne podatke.

Vrstice v indeksih imajo lahko tudi zelo različne strukture, odvisno od vrste indeksa. Na primer, za drevo B vrstice, povezane z listnimi stranmi, vsebujejo vrednost ključa indeksiranja in sklic (ctid) na ustrezno vrstico tabele. Na splošno je lahko indeks strukturiran na popolnoma drugačen način.

Najpomembnejša točka je, da v indeksih katere koli vrste ni različic vrstic. No, lahko pa domnevamo, da je vsaka vrstica predstavljena z natanko eno različico. Z drugimi besedami, v glavi indeksne vrstice ni polj xmin in xmax. Lahko domnevamo, da povezave iz indeksa vodijo do vseh različic tabele vrstic - tako da lahko ugotovite, katero različico bo transakcija videla samo s pogledom na tabelo. (Kot vedno to ni vsa resnica. V nekaterih primerih lahko zemljevid vidnosti optimizira postopek, vendar si bomo to podrobneje ogledali pozneje.)

Hkrati pa na indeksni strani najdemo napotke na obe različici, tako na sedanjo kot na staro:

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

Virtualne transakcije

V praksi PostgreSQL uporablja optimizacije, ki mu omogočajo "shranjevanje" številk transakcij.

Če transakcija samo bere podatke, to ne vpliva na vidnost različic vrstic. Zato servisni proces transakciji najprej izda virtualni xid. Številka je sestavljena iz ID-ja procesa in zaporedne številke.

Izdaja te številke ne zahteva sinhronizacije med vsemi procesi in je zato zelo hitra. Še en razlog za uporabo virtualnih številk bomo spoznali, ko bomo govorili o zamrznitvi.

Navidezne številke se v posnetkih podatkov nikakor ne upoštevajo.

V različnih časovnih obdobjih so lahko v sistemu virtualne transakcije s številkami, ki so že bile uporabljene, in to je normalno. Toda takšne številke ni mogoče zapisati v podatkovne strani, ker lahko ob naslednjem dostopu do strani izgubi ves pomen.

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

Če transakcija začne spreminjati podatke, dobi pravo, edinstveno številko transakcije.

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

=> COMMIT;

Ugnezdene transakcije

Shranite točke

Definirano v SQL prihranite točke (savepoint), ki vam omogočajo preklic dela transakcije, ne da bi jo popolnoma prekinili. Toda to se ne ujema z zgornjim diagramom, saj ima transakcija enak status za vse svoje spremembe in fizično nobeni podatki niso povrnjeni nazaj.

Za izvedbo te funkcionalnosti je transakcija s točko shranjevanja razdeljena na več ločenih ugnezdene transakcije (podtransakcija), katere status lahko upravljate ločeno.

Ugnezdene transakcije imajo svojo številko (višjo od številke glavne transakcije). Stanje ugnezdenih transakcij se beleži na običajen način v XACT, končni status pa je odvisen od statusa glavne transakcije: če je ta preklicana, so preklicane tudi vse ugnezdene transakcije.

Informacije o gnezdenju transakcij so shranjene v datotekah v imeniku PGDATA/pg_subtrans. Do datotek se dostopa prek medpomnilnikov v skupnem pomnilniku instance, organiziranih na enak način kot medpomnilniki XACT.

Ne zamenjujte ugnezdenih transakcij z avtonomnimi transakcijami. Avtonomne transakcije niso na noben način odvisne ena od druge, ugnezdene pa so. V običajnem PostgreSQL ni avtonomnih transakcij in morda najboljše: potrebne so zelo, zelo redko, njihova prisotnost v drugih DBMS-jih pa povzroča zlorabo, zaradi katere potem vsi trpijo.

Počistimo tabelo, začnimo transakcijo in vstavimo vrstico:

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

Zdaj postavimo točko shranjevanja in vstavimo drugo vrstico.

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

Upoštevajte, da funkcija txid_current() vrne glavno številko transakcije, ne ugnezdene številke transakcije.

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

Vrnimo se nazaj na točko shranjevanja in vstavimo tretjo vrstico.

=> 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 strani še naprej vidimo vrstico, ki jo je dodala preklicana ugnezdena transakcija.

Popravimo spremembe.

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

Zdaj lahko jasno vidite, da ima vsaka ugnezdena transakcija svoj status.

Upoštevajte, da ugnezdenih transakcij ni mogoče izrecno uporabiti v SQL, kar pomeni, da ne morete začeti nove transakcije, ne da bi dokončali trenutno. Ta mehanizem se implicitno aktivira pri uporabi točk shranjevanja, kot tudi pri obravnavanju izjem PL/pgSQL in v številnih drugih, bolj eksotičnih primerih.

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

Napake in atomičnost operacij

Kaj se zgodi, če med izvajanjem operacije pride do napake? Na primer takole:

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

Prišlo je do napake. Zdaj se transakcija šteje za prekinjeno in v njej niso dovoljene nobene operacije:

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

In tudi če poskusite potrditi spremembe, bo PostgreSQL poročal o prekinitvi:

=> COMMIT;
ROLLBACK

Zakaj se transakcija po neuspehu ne more nadaljevati? Dejstvo je, da lahko pride do napake tako, da bi dobili dostop do dela sprememb - kršena bi bila atomičnost niti ne transakcije, ampak operaterja. Kot v našem primeru, kjer je operaterju uspelo posodobiti eno vrstico pred napako:

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

Povedati je treba, da ima psql način, ki še vedno omogoča nadaljevanje transakcije po napaki, kot da bi bila dejanja napačnega operaterja vrnjena nazaj.

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

Ni težko uganiti, da v tem načinu psql dejansko postavi implicitno točko shranjevanja pred vsak ukaz in v primeru napake sproži povrnitev nanjo. Ta način ni privzeto uporabljen, saj nastavitev shranjevalnih točk (tudi brez povrnitve nanje) vključuje precejšnje stroške.

Nadaljevanje.

Vir: www.habr.com

Dodaj komentar