MVCC-3. Verzije redaka

Dakle, razmatrali smo pitanja vezana uz izolaciju, i napravili smo odmak o organizaciji podataka na niskoj razini. I napokon dolazimo do najzanimljivijeg — do verzija redaka.

Zaglavlje

Kao što smo već spomenuli, svaki redak može istovremeno postojati u bazi podataka u nekoliko verzija. Moramo na neki način razlikovati jednu verziju od druge. U tu svrhu svaka verzija ima dvije oznake koje definiraju "vrijeme" trajanja te verzije (xmin i xmax). U navodnicima — jer se ne koristi vrijeme kao takvo, već poseban rastući brojčanik. A taj brojač — broj transakcije.

(Kao i obično, zapravo je sve složenije: broj transakcija ne može se neprekidno povećavati zbog ograničenog kapaciteta brojača. No te detalje ćemo razmotriti detaljnije kada dođemo do zamrzavanja.)

Kada se redak kreira, vrijednost xmin postavlja se na broj transakcije koja je izvršila INSERT, dok se xmax ne popunjava.

Kada se redak briše, vrijednost xmax trenutne verzije označava se brojem transakcije koja je izvršila DELETE.

Kada se redak mijenja naredbom UPDATE, zapravo se izvode dvije operacije: DELETE i INSERT. U trenutnoj verziji retka postavlja se xmax, koji odgovara broju transakcije koja je izvršila UPDATE. Zatim se stvara nova verzija istog retka; vrijednost xmin je identična vrijednosti xmax prethodne verzije.

Polja xmin i xmax uključena su u zaglavlje verzije retka. Osim ovih polja, zaglavlje sadrži i druga, primjerice:

  • infomask — niz bitova koji definiraju svojstva ove verzije. Ima ih prilično puno; osnovne ćemo postupno razmatrati.
  • ctid — referenca na sljedeću, noviju verziju istog retka. Kod najnovije, aktualne verzije retka, ctid upućuje na samu ovu verziju. Broj ima oblik (x,y), gdje je x broj stranice, a y redni broj pokazivača u nizu.
  • bitna mapa neodređenih vrijednosti — označava one stupce ove verzije koji sadrže neodređenu vrijednost (NULL). NULL nije jedna od uobičajenih vrijednosti tipova podataka, stoga se podatak mora čuvati odvojeno.

Kao rezultat, zaglavlje ispada prilično veliko — najmanje 23 bajta za svaku verziju retka, a obično više zbog bitne karte NULL-ova. Ako je tablica "uska" (to jest, sadrži malo stupaca), troškovi mogu biti veći od korisnih informacija.

Umetanje

Pogledajmo detaljnije kako se nisko-nivo operacije sa stringovima izvode i počnimo od umetanja.

Za eksperimente stvorit ćemo novu tablicu s dvije kolone i indeks po jednoj od njih:

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

Umetnut ćemo jedan red, prethodno započinjući transakciju.

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

Evo broja naše trenutne transakcije:

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

Pogledajmo sadržaj stranice. Funkcija heap_page_items ekstenzije pageinspect omogućuje dobivanje informacija o pokazivačima i verzijama redova:

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

Primijetimo da se riječ heap (gomila) u PostgreSQL-u koristi za označavanje tablica. Ovo je još jedno čudno korištenje termina — gomila je poznata struktura podataka, koja nema nikakve veze s tablicom. Ovdje se ta riječ koristi u smislu 'sve je bačeno u gomilu', za razliku od urednih indeksa.

Funkcija pokazuje podatke 'kakvi jesu', u formatu koji je težak za razumjeti. Da bismo to shvatili, ostavit ćemo samo dio informacija i dešifrirati 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)

Evo što smo napravili:

  • Dodali smo nulu na broj pokazivača, kako bismo ga doveli u isti oblik kao t_ctid: (broj stranice, broj pokazivača).
  • Dešifrirali smo stanje pokazivača lp_flags. Ovdje je on 'normal' — to znači da se pokazivač zaista odnosi na verziju reda. Ostale vrijednosti razmotrit ćemo kasnije.
  • Od svih informativnih bitova izdvojili smo dosad samo dva para. Bitovi xmin_committed i xmin_aborted pokazuju je li transakcija s brojem xmin bila fiksirana (poništena). Dva slična bita odnose se na transakciju s brojem xmax.

Što vidimo? Kada umetnete redak na tabličnu stranicu, pojavit će se pokazivač s brojem 1, koji se odnosi na prvu i jedinu verziju retka.

U verziji retka polje xmin ispunjeno je brojem trenutne transakcije. Transakcija je još aktivna, pa nisu postavljena oba bita xmin_committed i xmin_aborted.

Polje ctid verzije retka odnosi se na taj isti redak. To znači da ne postoji novija verzija.

Polje xmax ispunjeno je lažnim brojem 0, budući da ova verzija retka nije izbrisana i aktuelna je. Transakcije neće obraćati pažnju na taj broj, budući da je postavljen bit xmax_aborted.

Napravimo još jedan korak ka poboljšanju čitljivosti, dodajući informacijske bitove brojevima transakcija. I stvorimo funkciju, jer će nam upit još jedanput zatrebati:

=> 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 'neupotrebljeno'
         WHEN 1 THEN 'normalno'
         WHEN 2 THEN 'preusmjereno na ' || lp_off
         WHEN 3 THEN 'mrtvo'
       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;

U ovom obliku znatno je jasnije što se događa u zaglavlju verzije retka:

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

Slične, ali znatno manje detaljne informacije mogu se dobiti i iz same tablice, koristeći psevdostupce xmin i xmax:

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

Fiksacija

Kada se transakcija uspješno završi, potrebno je zapamtiti njen status — označiti da je zabilježena. Za to se koristi struktura koja se naziva XACT (a do verzije 10 nazvana je CLOG (commit log) i ta se oznaka još može sresti na različitim mjestima).

XACT — nije tablica sistemskog kataloga; to su datoteke u katalogu PGDATA/pg_xact. U njima je za svaku transakciju dodijeljeno dva bita: committed i aborted — baš kao i u zaglavlju verzije retka. Ove informacije su raspoređene na nekoliko datoteka isključivo radi praktičnosti, još ćemo se vratiti na ovo pitanje kada budemo razmatrali zamrzavanje. Rad sa tim datotekama odvija se po stranicama, kao i sa svim ostalima.

Dakle, pri zaključenju transakcije u XACT postavlja se bit committed za ovu transakciju. I to je sve što se događa pri zaključenju (naravno, još ne govorimo o dnevniku predzapisivanja).

Kada bilo koja druga transakcija pristupi tabličnoj stranici na koju smo upravo gledali, morat će odgovoriti na nekoliko pitanja.

  1. Je li transakcija xmin završila? Ako nije, tada stvorena verzija retka ne bi smjela biti vidljiva.
    Takva provjera se vrši pregledavanjem još jedne strukture koja se nalazi u zajedničkoj memoriji instancije i zove se ProcArray. U njoj se nalazi popis svih aktivnih procesa, a za svaki je naveden broj njegove trenutne (aktivne) transakcije.
  2. Ako je završila, na koji način — zaključenjem ili poništenjem? Ako je poništenjem, tada verzija retka također ne bi smjela biti vidljiva.
    I upravo zato je potreban XACT. Međutim, iako se posljednje stranice XACT-a čuvaju u međuspremnicima u radnoj memoriji, stalno provjeravanje XACT-a je skuplje. Stoga, jednom utvrđeni status transakcije zapisuje se u bite xmin_committed i xmin_aborted verzije retka. Ako je jedan od tih bitova postavljen, stanje transakcije xmin se smatra poznatim i sljedećoj transakciji neće biti potrebno obraćati se XACT-u.

Zašto ti bitovi ne postavljaju sama transakcija koja izvršava umetanje? Kada se događa umetanje, transakcija još ne zna hoće li uspješno završiti. A u trenutku zaključenja već nije jasno koje su točno retke u kojim su točno stranicama promijenjene. Takvih stranica može biti mnogo, a njihov zapis nije isplativ. Uz to, dio stranica može biti izbačen iz međuspremnika na disk; ponovno čitanje njih kako bi se promijenili bitovi značilo bi značajno usporiti zaključivanje.

Druga strana ekonomije leži u tome što nakon promjena bilo koja transakcija (čak i ona koja izvršava jednostavno čitanje — SELECT) može početi mijenjati stranice podataka u međuspremniku.

Dakle, zaključimo promjenu.

=> COMMIT;

Na stranici se ništa nije promijenilo (ali znamo da je status transakcije već zabilježen u XACT-u):

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

Sada bi transakcija koja je prva pristupila stranici trebala odrediti status transakcije xmin i zabilježiti ga u informativnim bitovima:

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

Prilikom brisanja reda u polju xmax, trenutna verzija zapisuje broj tekuće transakcije koja briše, dok se bit xmax_aborted resetira.

Primijetimo da postavljena vrijednost xmax, koja odgovara aktivnoj transakciji, služi kao zaključavanje reda. Ako druga transakcija pokuša ažurirati ili obrisati ovaj red, morat će pričekati završetak transakcije xmax. O blokadama ćemo detaljnije govoriti kasnije. Za sada, naglasimo da broj blokada redova nije ograničen. One ne zauzimaju prostor u radnoj memoriji i performanse sustava ne pate zbog njihovog broja. Istina, 'duge' transakcije imaju druge nedostatke, ali o tome kasnije.

Obrišimo red.

=> POČNI;
=> OBRIŠI IZ t;
=> ODABERI txid_current();
 txid_current 
--------------
         3665
(1 red)

Vidimo da je broj transakcije zapisan u polju xmax, ali informacijski bitovi nisu postavljeni:

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

Otkazivanje

Otkazivanje promjena djeluje slično kao i potvrda, samo što se u XACT za transakciju postavlja bit aborted. Otkazivanje se izvodi jednako brzo kao i potvrda. Iako se naredba zove ROLLBACK, promjene se ne vraćaju: sve što je transakcija uspjela promijeniti u stranicama podataka ostaje nepromijenjeno.

=> ROLLBACK;
=> ODABERI * IZ heap_page('t',0);
 ctid  | stanje  |   xmin   | xmax | t_ctid 
-------+--------+----------+------+--------
 (0,1) | normal | 3664 (c) | 3665 | (0,1)
(1 red)

Prilikom pristupanja stranici provjerit će se status i u verziji reda postavit će se bit savjeta xmax_aborted. Sam broj xmax ostaje na stranici, ali ga više nitko neće gledati.

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

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

Ažuriranje

Ažuriranje djeluje kao da se prvo izvršilo brisanje trenutne verzije reda, a zatim umetanje nove.

=> POČNI;
=> AŽURIRAJ t POSTAVI s = 'BAR';
=> ODABERI txid_current();
 txid_current 
--------------
         3666
(1 red)

Upit daje jedan red (novu verziju):

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

Ali na stranici vidimo obje verzije:

=> SELECT * FROM heap_page('t', 0);
 ctid  | stanje  |   xmin   | xmax  | t_ctid 
-------+--------+----------+-------+--------
 (0,1) | normal | 3664 (c) | 3666  | (0,2)
 (0,2) | normal | 3666     | 0 (a) | (0,2)
(2 reda)

Izbrisana verzija označena je brojem trenutne transakcije u polju xmax. Osim toga, ova vrijednost je zapisana preko stare, budući da je prethodna transakcija poništena. A bit xmax_aborted je resetiran, jer status trenutne transakcije još uvijek nije poznat.

Prva verzija reda sada se odnosi na drugu (polje t_ctid), kao na noviju.

Na indeksnoj stranici pojavljuje se druga oznaka i druga linija koja se odnosi na drugu verziju u tabelarnoj stranici.

Kao i pri brisanju, vrijednost xmax u prvoj verziji linije služi kao pokazatelj da je linija zaključana.

I tako završavamo transakciju.

=> COMMIT;

Indeksi

Do sada smo govorili samo o tabelarnim stranicama. A što se događa unutar indeksa?

Informacije u indeksnim stranicama veoma ovise o konkretnom tipu indeksa. I čak kod jednog tipa indeksa postoje različite vrste stranica. Na primjer, B-stablo ima stranicu sa metapodacima i "obične" stranice.

Ipak, obično stranica sadrži niz pokazatelja na linije i same linije (kao u tabelarnoj stranici). Osim toga, na kraju stranice ostavlja se prostor za posebne podatke.

Linije u indeksima također mogu imati veoma različitu strukturu ovisno o tipu indeksa. Na primjer, za B-stablo, linije koje pripadaju listovnim stranicama sadrže vrijednost ključnog indeksa i referencu (ctid) na odgovarajuću liniju u tabeli. U općem slučaju, indeks može biti potpuno drugačije organiziran.

Najvažnija stvar je da u indeksima bilo kojeg tipa ne postoje verzije linija. Može se smatrati da je svaka linija predstavljena točno jednom verzijom. Drugim riječima, u zaglavlju indeksne linije nema polja xmin i xmax. Može se smatrati da veze iz indeksa vode na sve tabelarne verzije linija — pa se može razumjeti koju verziju će transakcija vidjeti jedino pogledom u tabelu. (Kao i obično, to nije cela istina. U nekim slučajevima, karta vidljivosti omogućava optimizaciju procesa, ali ćemo to detaljnije razmotriti kasnije.)

Pri tome u indeksnoj stranici otkrivamo pokazatelje na obje verzije, kako na aktualnu, tako i na staru:

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

Virtualne transakcije

U praksi PostgreSQL koristi optimizaciju koja omogućava "štednju" brojeva transakcija.

Ako transakcija samo čita podatke, ona nikako ne utječe na vidljivost verzija linija. Stoga u početku servisni proces dodijeljuje transakcijama virtualni broj (virtual xid). Broj se sastoji od identifikatora procesa i sekvencijalnog broja.

Izdavanje ovog broja ne zahtijeva sinkronizaciju između svih procesa, stoga se izvodi vrlo brzo. S drugom uzrokom korištenja virtualnih brojeva upoznat ćemo se kada budemo govorili o zamrzavanju.

Virtualni brojevi se nimalo ne uzimaju u obzir u снимцima podataka.

U različitim vremenskim trenucima u sustavu mogu se nalaziti virtualne transakcije s brojevima koji su već korišteni, i to je normalno. No takav broj se ne smije zapisivati u stranice podataka, jer prilikom sljedećeg pristupa stranici može izgubiti svako značenje.

=> POČNI;
=> ODABERI txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                         
(1 red)

Ako transakcija počne mijenjati podatke, dodjeljuje joj se pravi, jedinstveni broj transakcije.

=> AŽURIRAJ račune SET iznos = iznos - 1.00;
=> ODABERI txid_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                     3667
(1 red)

=> COMMIT;

Ugniježdene transakcije

Točke spremanja

U SQL-u su definirane točke spremanja (savepoint), koje omogućuju da se otkaže dio operacija transakcije, bez potpunog prekinutja. No to ne odgovara gornjoj shemi, budući da status transakcije ostaje jedan za sve njezine promjene, a fizički se nijedni podaci ne vraćaju.

Kako bi se realizirala takva funkcionalnost, transakcija s točkom spremanja razdvaja se na nekoliko zasebnih ugniježdenih transakcija (subtransaction), čijim se statusom može upravljati zasebno.

Ugniježdene transakcije imaju svoj vlastiti broj (veći od broja glavne transakcije). Status ugniježdenih transakcija se zapisuje na uobičajen način u XACT-u, ali konačni status ovisi o statusu glavne transakcije: ako je ona otkazana, tada se otkazuju i sve ugniježdene transakcije.

Informacije o ugniježdenosti transakcija čuvaju se u datotekama u direktoriju PGDATA/pg_subtrans. Pristup datotekama odvija se putem međuspremnika u zajedničkoj memoriji instance, organiziranih kao i međuspremnici XACT.

Nemojte brkati ugniježdene transakcije i autonomne transakcije. Autonomne transakcije ne ovise jedna o drugoj, dok ugniježdene — ovise. Autonomne transakcije u običnom PostgreSQL-u ne postoje, i možda je to najbolje: u suštini su potrebne vrlo rijetko, a njihova prisutnost u drugim DBMS-ima potiče zloupotrebu od koje svi kasnije pate.

Očistit ćemo tablicu, započeti transakciju i umetnuti redak:

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

Sada ćemo postaviti točku spremanja i umetnuti još jedan red.

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

Primijetite da funkcija txid_current() vraća broj glavne, a ne ugniježdene 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)

Vratimo se na točku spremanja i umetnemo treći red.

=> 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 stranici još uvijek vidimo redak koji je dodan odbijenom ugniježdenom transakcijom.

Fiksiramo promjene.

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

Sada je jasno vidljivo da svaka ugniježdena transakcija ima svoj status.

Primijetite da se ugniježdene transakcije ne mogu izravno koristiti u SQL-u, što znači da ne možete započeti novu transakciju bez završetka trenutne. Ovaj mehanizam se neizravno aktivira korištenjem točaka spremanja, kao i prilikom obrade iznimaka u PL/pgSQL-u i u nizu drugih, egzotičnijih slučajeva.

=> BEGIN;
POČNI
=> BEGIN;
UPUTE: već postoji transakcija u tijeku
BEGIN
=> COMMIT;
COMMIT
=> COMMIT;
UPUTE: nema transakcije u tijeku
COMMIT

Greške i atomarnost operacija

Što će se dogoditi ako dođe do greške tijekom izvršavanja operacije? Na primjer, ovako:

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

=> UPDATE t SET s = repeat('X', 1/(id-4));
GREŠKA:  dijeljenje s nulom

Došlo je do greške. Sada se transakcija smatra prekinutom i nijedna operacija unutar nje nije dopuštena:

=> SELECT * FROM t;
GREŠKA:  trenutna transakcija je prekinuta, naredbe će biti ignorirane do kraja bloka transakcija

A čak i ako pokušate fiksirati promjene, PostgreSQL će prijaviti prekid:

=> COMMIT;
ROLLBACK

Zašto se ne može nastaviti izvršenje transakcije nakon greške? Razlog tome je što je greška mogla nastati na način da bismo dobili pristup delu promena — načela atomarnosti bi bila narušena čak i ne transakcije, već operatera. Kao u našem primeru, gde je operater pre greške uspeo da ažurira jedan red.

=> SELECT * FROM heap_page('t', 0);
 ctid  | stanje  |   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 reda)

Treba reći da u psql postoji režim koji svejedno omogućava nastavak rada transakcije nakon greške kao da se radnja grešnog operatera poništava.

=> 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));
GREŠKA:  dijeljenje s nulom

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

=> COMMIT;

Nije teško pogoditi da u takvom režimu psql zapravo postavlja implicitnu tačku sačuvavanja ispred svake komande, a u slučaju greške inicira povratak na nju. Ovaj režim se ne koristi po defaultu, jer postavljanje tačaka sačuvavanja (čak i bez vraćanja na njih) nosi značajne troškove.

Nastavak.

Izvor: habr.com

Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere 🔥 Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere | ProHoster