Tako, obravnali smo vprašanja, povezana z , in naredili odstopanje o . In končno smo prišli do najbolj zanimivega – do verzij vrstic.
Zaglavlje
Kot smo že omenili, lahko vsaka vrstica hkrati obstaja v podatkovni bazi v več različicah. Te različice moramo nekako ločiti. Za ta namen ima vsaka različica dve oznaki, ki določata »čas« veljavnosti te različice (xmin in xmax). V narekovajih – ker se ne uporablja čas kot tak, temveč poseben naraščajoči števec. Ta števec je številka transakcije.
(Kot običajno, je v resnici vse bolj zapleteno: številke transakcij ne morejo nenehno naraščati zaradi omejene razsežnosti števca. Toda te podrobnosti bomo podrobno obravnavali, ko pridemo do zamrzovanja.)
Ko je vrstica ustvarjena, se vrednost xmin nastavi na številko transakcije, ki je izvedla ukaz INSERT, xmax pa se ne napolni.
Ko se vrstica izbriše, se vrednost xmax trenutne različice označi z številko transakcije, ki je izvedla DELETE.
Ko se vrstica spremeni z ukazom UPDATE, se dejansko izvede dve operaciji: DELETE in INSERT. V trenutni različici vrstice se nastavitev xmax izenači s številko transakcije, ki je izvedla UPDATE. Nato se ustvari nova različica iste vrstice; vrednost xmin pri njej je enaka vrednosti xmax prejšnje različice.
Polja xmin in xmax so del glave različice vrstice. Poleg teh polj glava vsebuje še druge, na primer:
- infomask – vrsta bitov, ki določajo lastnosti te različice. Teh je kar precej; osnovne izmed njih bomo postopoma obravnavali.
- ctid – sklic na naslednjo, novejšo različico iste vrstice. Pri najsodobnejši, aktualni, različici vrstice ctid kaže na točno to različico. Številka ima obliko (x,y), pri čemer je x številka strani, y pa zaporedna številka kazalca v matriki.
- bitna karta neodločenih vrednosti – označuje tiste stolpce te različice, ki vsebujejo neodločeno vrednost (NULL). NULL ni ena izmed običajnih vrednosti podatkovnih tipov, zato je treba to oznako hraniti ločeno.
Zaradi tega glava postane precej velika – najmanj 23 bajtov na vsako različico vrstice, običajno pa več zaradi bitne karte NULL. Če je tabela «ozka» (to pomeni, da vsebuje malo stolpcev), pa lahko stroški presegajo uporabne informacije.
Vstavljanje
Podrobneje si poglejmo, kako se izvajajo operacije s potrebami na nizki ravni, in začnimo z vstavljanjem.
Za eksperiment bomo ustvarili novo tabelo z dvema stolpcema in indeksom po enem od njih:
=> USTVARI TABELO t(
id serial,
s text
);
=> USTVARI INDEX NA t(s);
Vstavljamo en niz, preden začnemo transakcijo.
=> ZAČNI;
=> VSTAVI V t(s) VREDNOSTI ('FOO');
Tukaj je številka naše trenutne transakcije:
=> IZBERI txid_current();
txid_current
--------------
3664
(1 vrstica)
Poglejmo vsebino strani. Funkcija heap_page_items razširitve pageinspect omogoča pridobitev informacij o kazalih in različicah nizov:
=> IZBERI * IZ heap_page_items(get_raw_page('t',0)) gx
-[ ZAPIS 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
Opazimo, da se z besedo heap (kup) v PostgreSQL označujejo tabele. To je še ena čudna raba termina - kup je znana , ki nima nič skupnega s tabelo. Tu se beseda uporablja v pomenu „vse je zmešano“, v nasprotju s urejenimi indeksi.
Funkcija prikazuje podatke „takšne, kot so“, v težko razumljivem formatu. Da bi razjasnili, bomo obdržali le del informacij in jih razložili:
=> IZBERI '(0,'||lp||')' KOT ctid,
CASE lp_flags
KDA lp_flags = 0 POTEM 'neuporabljeno'
KDA lp_flags = 1 POTEM 'normalno'
KDA lp_flags = 2 POTEM 'preusmeritev na '||lp_off
KDA lp_flags = 3 POTEM 'mrtvo'
KONEC AS stanje,
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
IZ heap_page_items(get_raw_page('t',0)) gx
-[ ZAPIS 1 ]-+-------
ctid | (0,1)
stanje | normalno
xmin | 3664
xmax | 0
xmin_commited | f
xmin_aborted | f
xmax_commited | f
xmax_aborted | t
t_ctid | (0,1)
Tukaj je, kaj smo naredili:
- Dodali smo ničlo k številki kazalca, da smo jo privedli v enak položaj kot t_ctid: (številka strani, številka kazalca).
- Razložili smo stanje kazalca lp_flags. Tu je „normalno“ — to pomeni, da kazalec dejansko kaže na različico niza. Oglejmo si druge vrednosti kasneje.
- Iz vseh informacijskih bitov smo za zdaj izpostavili le dve pari. Biti xmin_committed in xmin_aborted kažejo, ali je bila transakcija z številko xmin potrjena (ali preklicana). Dva podobna bita se nanašata na transakcijo z številko xmax.
Kaj vidimo? Ko vstavljamo vrstico na tabelarično stran, se prikaže kazalec z številko 1, ki se nanaša na prvo in edino različico vrstice.
V različici vrstice je polje xmin napolnjeno s številko trenutne transakcije. Transakcija je še aktivna, zato sta oba bitka xmin_committed in xmin_aborted ne nastavljena.
Polje ctid različice vrstice se nanaša na to isto vrstico. To pomeni, da ne obstaja novejša različica.
Polje xmax je napolnjeno z lažno številko 0, saj ta različica vrstice ni odstranjena in je aktualna. Transakcije ne bodo pozorne na to številko, ker je bit xmax_aborted nastavljen.
Naredimo še en korak k izboljšanju berljivosti, tako da dodamo informacijske bite k številkam transakcij. In ustvarimo funkcijo, saj bo poizvedba še enkrat potrebna:
=> 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 precej bolj jasno, kaj se dogaja v glavi različice vrstice:
=> SELECT * FROM heap_page('t',0);
ctid | stanje | xmin | xmax | t_ctid
-------+--------+------+-------+--------
(0,1) | normal | 3664 | 0 (a) | (0,1)
(1 vrstica)
Podobne, a bistveno manj podrobne informacije je mogoče pridobiti tudi iz same tabele, z uporabo psevdostolpcev xmin in xmax:
=> SELECT xmin, xmax, * FROM t;
xmin | xmax | id | s
------+------+----+-----
3664 | 0 | 1 | FOO
(1 vrstica)
Zapisovanje
Ob uspešnem zaključku transakcije je potrebno zapomniti njen status — označiti, da je bila potrjena. Za to se uporablja struktura, imenovana XACT (do različice 10 se je imenovala CLOG (commit log) in ta izraz se še lahko srečuje na različnih mestih).
XACT — ni tabela sistemskega kataloga; to so datoteke v imeniku PGDATA/pg_xact. Za vsako transakcijo sta dodeljena dva bitka: committed in aborted — prav tako kot v glavi različice vrstice. Ta informacija je razdeljena med več datotek izključno zaradi udobja, o tej temi se bomo še vrnili, ko bomo obravnavali zamrznitev. Delo s temi datotekami poteka po straneh, tako kot z vsemi drugimi.
Ko sem fiksirana transakcija v XACT, se za to transakcijo nastavi bit committed. In to je vse, kar se zgodi ob fiksaciji (res je, da zaenkrat ne govorimo o dnevniku predzapisov).
Ko se katerakoli druga transakcija obrne na tabelo, ki smo jo pravkar pregledali, bo morala odgovoriti na nekaj vprašanj.
- Ali je bila transakcija xmin zaključena? Če ne, nova različica vrstice ne bi smela biti vidna.
Ta preverba se izvede s pregledom še ene strukture, ki se nahaja v skupni pomnilniku primera in se imenuje ProcArray. V njej je seznam vseh aktivnih procesov, in za vsak je navedeno število njegove trenutne (aktivne) transakcije. - Če je zaključena, kako — s fiksacijo ali odpovedjo? Če z odpovedjo, potem nova različica vrstice tudi ne bi smela biti vidna.
Za to je pravzaprav potreben XACT. Vendar, čeprav so zadnje strani XACT shranjene v pomnilniku, preverjanje XACT vsakokrat pomeni določene stroške. Zato se enkrat ugotovljen status transakcije shrani v bite xmin_committed in xmin_aborted nove različice vrstice. Če je ena od teh bitov nastavljena, se stanje transakcije xmin šteje za znano in naslednji transakciji ne bo treba spet preverjati XACT.
Zakaj ti biti niso nastavljeni s strani transakcije, ki izvaja vstavljanje? Ko pride do vstavljanja, transakcija še ne ve, ali bo uspešna. In ob fiksaciji ni jasno, katere vrstice so bile spremenjene v katerih straneh. Takih strani je lahko veliko, in njihovo shranjevanje ni dobičkonosno. Poleg tega se lahko del strani izbriše iz pomnilniškega predpomnilnika na disk; ponovno branje, da bi spremenili bite, bi znatno upočasnilo fiksacijo.
Obratna stran varčevanja je, da po spremembah lahko katerakoli transakcija (tudi tista, ki preprosto bere — SELECT) začne spreminjati strani podatkov v pomnilniškem predpomnilniku.
Torej, potrdimo spremembo.
=> COMMIT;
Na strani se ni spremenilo nič (a vemo, da je status transakcije že shranjen v XACT):
=> SELECT * FROM heap_page('t',0);
ctid | stanje | xmin | xmax | t_ctid
-------+--------+------+-------+--------
(0,1) | normal | 3664 | 0 (a) | (0,1)
(1 vrstica)
Zdaj bo morala transakcija, ki se je prva obrnila na to stran, določiti status transakcije xmin in ga zapisati v informacijske bite:
=> SELECT * FROM t;
id | s
----+-----
1 | FOO
(1 vrstica)
=> SELECT * FROM heap_page('t',0);
ctid | stanje | xmin | xmax | t_ctid
-------+--------+----------+-------+--------
(0,1) | normal | 3664 (c) | 0 (a) | (0,1)
(1 vrstica)
Odstranjevanje
При удалении строки в поле xmax актуальной версии записывается номер текущей удаляющей транзакции, а бит xmax_aborted сбрасывается.
Заметим, что установленное значение xmax, соответствующее активной транзакции, выступает в качестве блокировки строки. Если другая транзакция собирается обновить или удалить эту строку, она будет вынуждена дождаться завершения транзакции xmax. Подробнее про блокировки мы будем говорить позже. Пока отметим только, что число блокировок строк ничем не ограничено. Они не занимают место в оперативной памяти и производительность системы не страдает от их количества. Правда, у “длинных” транзакций есть другие минусы, но об этом тоже позже.
Удалим строку.
=> BEGIN;
=> DELETE FROM t;
=> SELECT txid_current();
txid_current
--------------
3665
(1 row)
Видим, что номер транзакции записался в поле xmax, но информационные биты не установлены:
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+----------+------+--------
(0,1) | normal | 3664 (c) | 3665 | (0,1)
(1 row)
Отмена
Отмена изменений работает аналогично фиксации, только в XACT для транзакции выставляется бит aborted. Отмена выполняется так же быстро, как и фиксация. Хоть команда и называется ROLLBACK, отката изменений не происходит: все, что транзакция успела изменить в страницах данных, остается без изменений.
=> ROLLBACK;
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+----------+------+--------
(0,1) | normal | 3664 (c) | 3665 | (0,1)
(1 row)
При обращении к странице будет проверен статус и в версию строки будет установлен бит подсказки xmax_aborted. Сам номер xmax при этом остается в странице, но смотреть на него уже никто не будет.
=> SELECT * FROM t;
id | s
----+-----
1 | FOO
(1 vrstica)
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+----------+----------+--------
(0,1) | normal | 3664 (c) | 3665 (a) | (0,1)
(1 row)
Posodobitev
Обновление работает так, как будто сначала выполнилось удаление текущей версии строки, а затем вставка новой.
=> BEGIN;
=> UPDATE t SET s = 'BAR';
=> SELECT txid_current();
txid_current
--------------
3666
(1 row)
Запрос выдает одну строку (новую версию):
=> SELECT * FROM t;
id | s
----+-----
1 | BAR
(1 row)
Но в странице мы видим обе версии:
=> 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)
Удаленная версия помечена номером текущей транзакции в поле xmax. Причем это значение записано поверх старого, поскольку предыдущая транзакция была отменена. А бит xmax_aborted сброшен, так как статус текущей транзакции еще неизвестен.
Первая версия строки ссылается теперь на вторую (поле t_ctid), как на более новую.
Na indeksni strani se prikaže drugi kazalec in druga vrstica, ki se nanaša na drugo različico v tabelarni strani.
Tako kot pri brisanju, vrednost xmax v prvotni različici vrstice služi kot znak, da je vrstica zaklenjena.
In končali bomo transakcijo.
=> COMMIT;
Indeksi
Do sedaj smo govorili samo o tabelarnih straneh. Kaj pa se dogaja znotraj indeksov?
Informacije na indeksnih straneh so močno odvisne od konkretne vrste indeksa. In celo znotraj ene vrste indeksa so lahko različne vrste strani. Na primer, B-drevo ima stran z metapodatki in 'navadne' strani.
Kljub temu običajno stran vsebuje niz kazalcev na vrstice in te vrstice (kot tudi v tabelarni strani). Poleg tega je na koncu strani reserved prostor za posebne podatke.
Vrstice v indeksih lahko tudi imajo zelo različne strukture, odvisno od vrste indeksa. Na primer, za B-drevo vrstice, ki se nanašajo na listne strani, vsebujejo vrednost ključnega indeksa in povezavo (ctid) na ustrezno vrstico tabele. Na splošno je lahko indeks zasnovan popolnoma drugače.
Najpomembnejša točka je, da v indeksih katere koli vrste ni različic vrstic. Ali pa lahko menimo, da je vsaka vrstica predstavljena z natančno eno različico. Z drugimi besedami, v glavi indeksne vrstice ni polj xmin in xmax. Lahko menimo, da povezave iz indeksa vodijo do vseh tabelarnih različic vrstic — tako da lahko ugotovimo, katero različico bo transakcija videla, le tako, da pogledamo v tabelo. (Kot običajno, to ni cela resnica. V nekaterih primerih lahko karta vidnosti optimizira postopek, toda podrobneje bomo to obravnavali kasneje.)
Pri tem na indeksni strani odkrijemo kazalce na obe različici, tako na aktualno 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 optimizacijo, ki omogoča, da "prihrani" številke transakcij.
Če transakcija samo bere podatke, potem ne vpliva na vidnost različic vrstic. Zato ob začetku servisni proces dodeli transakcijam virtualno številko (virtual xid). Številka je sestavljena iz identifikatorja procesa in zaporedne številke.
Izdavanje ovog broja ne zahteva sinhronizaciju između svih procesa i stoga se izvršava vrlo brzo. Drugi razlog za korišćenje virtuelnih brojeva biće objašnjen kada budemo govorili o zamrzavanju.
Virtuelni brojevi se uopšte ne uzimaju u obzir u snimcima podataka.
U različitim trenucima u sistemu mogu se nalaziti virtuelne transakcije s brojevima koji su već korišćeni, i to je normalno. Međutim, takav broj ne može se zapisivati na stranice podataka, jer prilikom sledećeg pristupa stranici može izgubiti svako značenje.
=> POČNI;
=> IZABERI txid_current_if_assigned();
txid_current_if_assigned
--------------------------
(1 red)
Ako transakcija počne da menja podatke, dodeljuje joj se pravi, jedinstveni broj transakcije.
=> AŽURIRAJ račune POSTAVI iznos = iznos - 1.00;
=> IZABERI txid_current_if_assigned();
txid_current_if_assigned
--------------------------
3667
(1 red)
=> COMMIT;
Ugnježdene transakcije
Tačke čuvanja
U SQL-u su definisane tačke čuvanja (savepoint), koje omogućavaju da se poništi deo operacija transakcije, ne prekinuvši je potpuno. Međutim, to se ne uklapa u prethodno navedenu šemu, pošto status transakcije važi za sve njene izmene, a fizički se nikakvi podaci ne vraćaju.
Da bi se realizovala takva funkcionalnost, transakcija sa tačkom čuvanja deli se na nekoliko posebno ugnježdenih transakcija (subtransaction), čijim se statusom može upravljati zasebno.
Ugnježdene transakcije imaju svoj vlastiti broj (veći od broja glavne transakcije). Status ugnježdenih transakcija se beleži na uobičajen način u XACT-u, međutim konačni status zavisi od statusa glavne transakcije: ako je ona poništena, poništavaju se i sve ugnježdene transakcije.
Informacije o ugnježdenosti transakcija čuvaju se u datotekama u katalogu PGDATA/pg_subtrans. Pristup datotekama se vrši putem bafera u zajedničkoj memoriji instancije, organizovanih na isti način kao i baferi XACT.
Ne mešajte ugnježdene transakcije i autonomne transakcije. Autonomne transakcije ne zavise jedna od druge, dok ugnježdene zavise. Autonomne transakcije u običnom PostgreSQL-u ne postoje, i, verovatno, bolje je to tako: one su potrebne vrlo, vrlo retko, a njihova prisutnost u drugim DBMS provokuje zloupotrebu od koje svi pate.
Očistiće tabelu, započeti transakciju i umetnuti red:
=> 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)
Zdajmo postavimo točko shranjevanja in vstavimo še en stavek.
=> SAVEPOINT sp;
=> INSERT INTO t(s) VALUES ('XYZ');
=> SELECT txid_current();
txid_current
--------------
3669
(1 row)
Opazite, da funkcija txid_current() vrne številko glavne, ne pa notranje 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)
Vrni se k točki shranjevanja in vstavimo tretji stavek.
=> 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 vedno vidimo vrstico, dodano prek preklicane notranje transakcije.
Potrdimo 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)
Sedaj je lepo vidno, da ima vsaka notranja transakcija svoj status.
Opazite, da notranje transakcije v SQL ni mogoče izrecno uporabiti, torej ne morete začeti nove transakcije, ne da bi dokončali trenutno. Ta mehanizem se aktivira implicitno ob uporabi točk shranjevanja, prav tako pri obdelavi napak PL/pgSQL in v nekaterih drugih bolj eksotičnih primerih.
=> BEGIN;
POČNI
=> BEGIN;
OPOZORILO: že poteka transakcija
BEGIN
=> COMMIT;
COMMIT
=> COMMIT;
OPOZORILO: ni transakcije v teku
COMMIT
Napake in atomarnost operacij
Kaj se zgodi, če pride do napake med izvajanjem operacije? Na primer, tako:
=> BEGIN;
=> SELECT * FROM t;
id | s
----+-----
2 | FOO
4 | BAR
(2 rows)
=> UPDATE t SET s = repeat('X', 1/(id-4));
NAPAKA: deljenje z nič
Prišlo je do napake. Zdaj se transakcija šteje za prekinjeno in nobena operacija v njej ni dovoljena:
=> SELECT * FROM t;
NAPAKA: trenutna transakcija je prekinjena, ukazi so ignorirani do konca transakcijskega bloka
In tudi če poskusite potrditi spremembe, PostgreSQL poroča o preklicu:
=> COMMIT;
ROLLBACK
Zakaj po napaki ni mogoče nadaljevati z izvajanjem transakcije? Gre za to, da se je napaka lahko pojavila na način, da bi dobili dostop do dela sprememb - kršila bi se atomarnost ne le transakcije, temveč tudi izvajalca. Kot v našem primeru, kjer je izvajalec pred napako uspel posodobiti en stavek:
=> 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)
Treba je povedati, da psql omogoča način, ki vseeno dovoljuje nadaljevanje dela transakcije po napaki, kot da se dejanja napak izvajalca razveljavijo.
=> 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));
NAPAKA: deljenje z nič
=> 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 vsako ukazno vrstico, in v primeru napake sproži povrnitev nanjo. Tak način se po privzetku ne uporablja, saj je postavitev točk shranjevanja (tudi brez povrnitve nanje) povezana z znatnimi stroški.
Izvor: habr.com
