MVCC-3. String verzije

Dakle, razmotrili smo pitanja koja se odnose na izolacija, i napravio povlačenje oko organizovanje podataka na niskom nivou. I konačno smo došli do najzanimljivijeg dijela - verzija sa žicama.

Header

Kao što smo već rekli, svaki red može istovremeno postojati u nekoliko verzija u bazi podataka. Jedna verzija se mora nekako razlikovati od druge.U tu svrhu svaka verzija ima dvije oznake koje određuju “vrijeme” djelovanja ove verzije (xmin i xmax). Pod navodnicima - jer se ne koristi vrijeme kao takvo, već poseban brojač povećanja. A ovaj brojač je broj transakcije.

(Kao i obično, stvarnost je složenija: broj transakcije se ne može stalno povećavati zbog ograničenog kapaciteta brojača. Ali ove detalje ćemo detaljno pogledati kada dođemo do zamrzavanja.)

Kada se kreira red, xmin se postavlja na broj transakcije koja je izdala naredbu INSERT, a xmax ostaje prazan.

Kada se red izbriše, xmax vrijednost trenutne verzije je označena brojem transakcije koja je izvršila DELETE.

Kada se red mijenja naredbom UPDATE, dvije operacije se zapravo izvode: DELETE i INSERT. Trenutna verzija reda postavlja xmax jednak broju transakcije koja je izvršila UPDATE. Zatim se kreira nova verzija istog stringa; njegova xmin vrijednost poklapa se sa xmax vrijednošću prethodne verzije.

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

  • infomaska ​​je niz bitova koji definiraju svojstva ove verzije. Ima ih dosta; Postepeno ćemo razmatrati glavne.
  • ctid je veza ka sljedećoj, novijoj verziji iste linije. Za najnoviju, najnoviju verziju linije, ctid se odnosi na samu ovu verziju. Broj ima oblik (x,y), gdje je x broj stranice, y je broj indeksa u nizu.
  • null bitmap - Označava one stupce date verzije koji sadrže null vrijednost (NULL). NULL nije jedna od normalnih vrijednosti tipa podataka, tako da se atribut mora pohraniti zasebno.

Kao rezultat, zaglavlje je prilično veliko - najmanje 23 bajta za svaku verziju linije, a obično više zbog NULL bitmape. Ako je tabela "uska" (to jest, sadrži nekoliko kolona), glavni troškovi mogu zauzeti više od korisnih informacija.

umetnuti

Pogledajmo bliže kako se izvode operacije niza niskog nivoa, počevši od umetanja.

Za eksperimente, napravimo novu tablicu s dvije kolone i indeksom na jednoj od njih:

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

Ubacimo jedan red nakon pokretanja transakcije.

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

Evo našeg trenutnog broja transakcije:

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

Pogledajmo sadržaj stranice. Funkcija heap_page_items ekstenzije pageinspect omogućava vam da dobijete informacije o pokazivačima i verzijama reda:

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

Imajte na umu da se riječ hrpa u PostgreSQL odnosi na tabele. Ovo je još jedna čudna upotreba termina - poznata je gomila struktura podataka, koji nema ništa zajedničko sa stolom. Ovdje se riječ koristi u značenju „sve je spojeno“, za razliku od uređenih indeksa.

Funkcija prikazuje podatke „kao što jesu“, u formatu koji je teško razumjeti. Da bismo to shvatili, ostavit ćemo samo dio informacija i dešifrirati ih:

=> 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 šta smo uradili:

  • Broju indeksa je dodana nula kako bi izgledao isto kao t_ctid: (broj stranice, broj indeksa).
  • Dešifrovano stanje pokazivača lp_flags. Ovdje je "normalno" - to znači da se pokazivač zapravo odnosi na verziju niza. Kasnije ćemo pogledati druga značenja.
  • Od svih bitova informacija, do sada su identificirana samo dva para. Bitovi xmin_committed i xmin_aborted pokazuju da li je broj transakcije xmin urezan (prekinut). Dva slična bita odnose se na transakcijski broj xmax.

šta vidimo? Kada umetnete red, na stranici tabele će se pojaviti indeks broj 1, koji ukazuje na prvu i jedinu verziju reda.

U verziji stringa, xmin polje se popunjava trenutnim brojem transakcije. Transakcija je još uvijek aktivna, tako da i xmin_committed i xmin_aborted bitovi nisu postavljeni.

Polje ctid verzije reda odnosi se na isti red. To znači da novija verzija ne postoji.

Polje xmax je popunjeno lažnim brojem 0 jer ova verzija reda nije obrisana i trenutna je. Transakcije neće obratiti pažnju na ovaj broj jer je postavljen bit xmax_aborted.

Napravimo još jedan korak ka poboljšanju čitljivosti dodavanjem bitova informacija brojevima transakcija. I kreirajmo funkciju, jer će nam zahtjev trebati više puta:

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

U ovom obliku, mnogo je jasnije šta se dešava u zaglavlju verzije reda:

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

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

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

Fiksacija

Ako je transakcija uspješno završena, morate zapamtiti njen status - imajte na umu da je predana. Za to se koristi struktura koja se zove XACT (a prije verzije 10 zvala se CLOG (dnevnik urezivanja) i ovo ime se još uvijek može naći na različitim mjestima).

XACT nije tabela sistemskog kataloga; ovo su datoteke u PGDATA/pg_xact direktoriju. Imaju dva bita za svaku transakciju: izvršeno i prekinuto - baš kao u zaglavlju verzije reda. Ove informacije su podijeljene u nekoliko datoteka isključivo radi pogodnosti; vratit ćemo se na ovo pitanje kada razmotrimo zamrzavanje. I rad sa ovim fajlovima se odvija stranicu po stranicu, kao i sa svim ostalim.

Dakle, kada je transakcija urezana u XACT-u, urezani bit se postavlja za ovu transakciju. I to je sve što se dešava tokom urezivanja (iako još ne govorimo o dnevniku pre snimanja).

Kada druga transakcija pristupi stranici tablice koju smo upravo pogledali, morat će odgovoriti na nekoliko pitanja.

  1. Da li je xmin transakcija završena? Ako nije, onda kreirana verzija niza ne bi trebala biti vidljiva.
    Ova provjera se izvodi gledanjem druge strukture, koja se nalazi u zajedničkoj memoriji instance i zove se ProcArray. Sadrži listu svih aktivnih procesa, a za svaki je naveden broj njegove trenutne (aktivne) transakcije.
  2. Ako je završeno, kako onda - obavezivanjem ili otkazivanjem? Ako se otkaže, onda ni verzija reda ne bi trebala biti vidljiva.
    To je upravo ono čemu služi XACT. Ali, iako su posljednje stranice XACT-a pohranjene u baferima u RAM-u, i dalje je skupo svaki put provjeriti XACT. Stoga, kada se odredi status transakcije, on se upisuje u bitove xmin_committed i xmin_aborted verzije stringa. Ako je jedan od ovih bitova postavljen, tada se stanje transakcije xmin smatra poznatim i sljedeća transakcija neće morati pristupiti XACT-u.

Zašto ovi bitovi nisu postavljeni od strane same transakcije koja vrši umetanje? Kada dođe do umetanja, transakcija još ne zna da li će uspjeti. A u trenutku urezivanja više nije jasno koji su redovi u kojim stranicama promijenjeni. Takvih stranica može biti puno, a njihovo pamćenje je neisplativo. Osim toga, neke stranice mogu biti izbačene iz keša bafera na disk; njihovo ponovno čitanje da bi se promijenili bitovi bi značajno usporilo urezivanje.

Loša strana uštede je što nakon promjena, svaka transakcija (čak i ona koja izvodi jednostavno čitanje - SELECT) može početi mijenjati stranice podataka u kešu bafera.

Dakle, popravimo promjenu.

=> COMMIT;

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

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

Sada će transakcija koja prva pristupa stranici morati odrediti status xmin transakcije i zapisati ga u bitove informacija:

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

Kada se red izbriše, broj trenutne transakcije brisanja se upisuje u xmax polje trenutne verzije, a bit xmax_aborted se briše.

Imajte na umu da postavljena vrijednost xmax koja odgovara aktivnoj transakciji djeluje kao zaključavanje reda. Ako druga transakcija želi ažurirati ili izbrisati ovaj red, bit će prisiljena čekati da se transakcija xmax završi. Kasnije ćemo više razgovarati o blokiranju. Za sada samo napominjemo da je broj zaključavanja redova neograničen. Oni ne zauzimaju prostor u RAM-u i performanse sistema ne pate od njihovog broja. Istina, “duge” transakcije imaju i druge nedostatke, ali o tome kasnije.

Hajde da izbrišemo liniju.

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

Vidimo da je broj transakcije upisan u xmax polje, ali bitovi informacija nisu postavljeni:

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

otkazivanje

Poništavanje promjena funkcionira slično kao i urezivanje, samo u XACT-u je prekinuti bit postavljen za transakciju. Poništavanje je brzo kao i preuzimanje. Iako se naredba zove ROLLBACK, promjene se ne poništavaju: sve što je transakcija uspjela promijeniti na stranicama podataka ostaje nepromijenjena.

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

Kada se pristupi stranici, status će biti provjeren i xmax_aborted hint bit će biti postavljen na verziju reda. Sam xmax broj ostaje na stranici, ali ga niko neće pogledati.

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

Ažuriraj

Ažuriranje radi kao da je prvo izbrisalo trenutnu verziju reda, a zatim umetnulo novu.

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

Upit proizvodi jedan red (nova verzija):

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

Ali na stranici vidimo obje verzije:

=> 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 verzija je označena trenutnim brojem transakcije u xmax polju. Štaviše, ova vrijednost se upisuje preko stare, jer je prethodna transakcija otkazana. I bit xmax_aborted se briše jer status trenutne transakcije još nije poznat.

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

Drugi indeks se pojavljuje na stranici indeksa, a drugi red upućuje na drugu verziju na stranici tablice.

Baš kao i kod brisanja, xmax vrijednost u prvoj verziji reda je indikacija da je red zaključan.

Pa, hajde da završimo transakciju.

=> COMMIT;

Indeksi

Do sada smo govorili samo o stranicama tabele. Šta se dešava unutar indeksa?

Informacije na stranicama indeksa uvelike variraju ovisno o specifičnoj vrsti indeksa. Čak i jedan tip indeksa ima različite tipove stranica. Na primjer, B-stablo ima stranicu sa metapodacima i "obične" stranice.

Međutim, stranica obično ima niz pokazivača na redove i same redove (baš kao stranica tabele). Osim toga, na kraju stranice postoji prostor za posebne podatke.

Redovi u indeksima također mogu imati vrlo različite strukture ovisno o tipu indeksa. Na primjer, za B-stablo, redovi koji se odnose na lisne stranice sadrže vrijednost ključa za indeksiranje i referencu (ctid) na odgovarajući red tablice. Općenito, indeks se može strukturirati na potpuno drugačiji način.

Najvažnija stvar je da ne postoje verzije reda u indeksima bilo kojeg tipa. Pa, ili možemo pretpostaviti da je svaki red predstavljen tačno jednom verzijom. Drugim riječima, nema xmin i xmax polja u zaglavlju reda indeksa. Možemo pretpostaviti da veze iz indeksa vode do svih verzija tabela redova - tako da možete shvatiti koju će verziju transakcija vidjeti samo gledanjem u tablicu. (Kao i uvijek, ovo nije cijela istina. U nekim slučajevima, mapa vidljivosti može optimizirati proces, ali ćemo to detaljnije pogledati kasnije.)

Istovremeno, na stranici indeksa nalazimo pokazivače na obje verzije, i trenutnu i staru:

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

Virtuelne transakcije

U praksi, PostgreSQL koristi optimizacije koje mu omogućavaju da „čuva“ brojeve transakcija.

Ako transakcija čita samo podatke, to nema utjecaja na vidljivost verzija redaka. Stoga, servisni proces prvo izdaje virtualni xid transakciji. Broj se sastoji od ID-a procesa i rednog broja.

Izdavanje ovog broja ne zahtijeva sinhronizaciju između svih procesa i stoga je vrlo brzo. Upoznat ćemo se sa još jednim razlogom za korištenje virtuelnih brojeva kada govorimo o zamrzavanju.

Virtuelni brojevi se ni na koji način ne uzimaju u obzir u snimcima podataka.

U različitim vremenskim trenucima, mogu postojati virtuelne transakcije u sistemu sa brojevima koji su već korišćeni, i to je normalno. Ali takav broj se ne može upisati u stranice s podacima, jer sljedeći put kada se pristupi stranici može izgubiti svako značenje.

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

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

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

=> COMMIT;

Ugniježđene transakcije

Sačuvaj bodove

Definirano u SQL-u uštedite bodove (savepoint), koji vam omogućavaju da otkažete dio transakcije bez potpunog prekidanja. Ali ovo se ne uklapa u gornji dijagram, budući da transakcija ima isti status za sve promjene i fizički se podaci ne vraćaju.

Za implementaciju ove funkcionalnosti, transakcija s točkom spremanja je podijeljena na nekoliko odvojenih ugniježđene transakcije (podtransakcija), čijim statusom se može upravljati zasebno.

Ugniježđene transakcije imaju svoj broj (veći od broja glavne transakcije). Status ugniježđenih transakcija se bilježi na uobičajen način u XACT-u, ali konačni status ovisi o statusu glavne transakcije: ako je poništena, onda se poništavaju i sve ugniježđene transakcije.

Informacije o ugniježđenju transakcija pohranjene su u datotekama u PGDATA/pg_subtrans direktoriju. Datotekama se pristupa preko bafera u zajedničkoj memoriji instance, organizovanih na isti način kao i XACT baferima.

Nemojte brkati ugniježđene transakcije sa autonomnim transakcijama. Autonomne transakcije ne zavise jedna od druge ni na koji način, ali ugniježđene transakcije zavise. U običnom PostgreSQL-u nema autonomnih transakcija, i, možda, najbolje: potrebne su vrlo, vrlo rijetko, a njihovo prisustvo u drugim DBMS-ovima izaziva zloupotrebe, od kojih onda svi trpe.

Očistimo tabelu, započnemo transakciju i ubacimo 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)

Sada stavimo tačku za spremanje i ubacimo drugu liniju.

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

Imajte na umu da funkcija txid_current() vraća glavni broj transakcije, a ne ugniježđeni broj 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 tačku čuvanja i ubacimo 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 nastavljamo vidjeti red koji je dodan otkazanom ugniježđenom transakcijom.

Popravljamo 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 možete jasno vidjeti da svaka ugniježđena transakcija ima svoj status.

Imajte na umu da se ugniježđene transakcije ne mogu eksplicitno koristiti u SQL-u, to jest, ne možete započeti novu transakciju bez dovršetka trenutne. Ovaj mehanizam se aktivira implicitno pri korištenju tačaka spremanja, kao i pri rukovanju PL/pgSQL izuzecima i u nizu drugih, egzotičnijih slučajeva.

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

Greške i atomičnost operacija

Šta se dešava ako dođe do greške prilikom izvođenja 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));
ERROR:  division by zero

Došlo je do greške. Sada se transakcija smatra prekinutom i nikakve operacije u njoj nisu dozvoljene:

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

Čak i ako pokušate urezati promjene, PostgreSQL će prijaviti prekid:

=> COMMIT;
ROLLBACK

Zašto se transakcija ne može nastaviti nakon neuspjeha? Činjenica je da bi mogla nastati greška na način da bismo dobili pristup dijelu izmjena – narušila bi se atomičnost čak i ne transakcije, već operatora. Kao u našem primjeru, gdje je operater uspio ažurirati jednu liniju prije greške:

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

Mora se reći da psql ima način rada koji i dalje dozvoljava nastavak transakcije nakon neuspjeha kao da su akcije pogrešnog operatora vraćene.

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

Nije teško pretpostaviti da u ovom režimu psql zapravo stavlja implicitnu tačku čuvanja ispred svake naredbe, a u slučaju neuspjeha inicira povratak na nju. Ovaj način rada se ne koristi prema zadanim postavkama, budući da postavljanje tačaka spremanja (čak i bez vraćanja na njih) uključuje značajne troškove.

Nastavak.

izvor: www.habr.com

Dodajte komentar