MVCC-3. Gudačke verzije

Dakle, razmotrili smo pitanja vezana uz izolacija, te se povukao oko organiziranje podataka na niskoj razini. I konačno smo došli do najzanimljivijeg dijela - gudačkih verzija.

Naslov

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

(Kao i obično, stvarnost je kompliciranija: broj transakcije ne može se stalno povećavati zbog ograničenog bitnog kapaciteta brojača. Ali detaljnije ćemo pogledati ove detalje 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 označava se brojem transakcije koja je izvršila DELETE.

Kada se red mijenja naredbom UPDATE, zapravo se izvode dvije operacije: DELETE i INSERT. Trenutna verzija retka postavlja xmax jednak broju transakcije koja je izvršila AŽURIRANJE. Zatim se stvara nova verzija istog niza; njegova xmin vrijednost podudara se s xmax vrijednošću prethodne verzije.

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

  • infomaska ​​je niz bitova koji definiraju svojstva ove verzije. Ima ih dosta; Postupno ćemo razmotriti glavne.
  • ctid je poveznica na sljedeću, noviju verziju istog retka. Za najnoviju, najnoviju verziju niza, 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 dane verzije koji sadrže null vrijednost (NULL). NULL nije jedna od uobičajenih vrijednosti tipa podataka, pa se atribut mora zasebno pohraniti.

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

umetnuti

Pogledajmo pobliže kako se izvode operacije niza niske razine, počevši od umetanja.

Za eksperimente, stvorimo novu tablicu s dva stupca i indeksom na jednom od njih:

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

Umetnimo jedan red nakon pokretanja transakcije.

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

Ovo je naš trenutni broj transakcije:

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

Pogledajmo sadržaj stranice. Funkcija heap_page_items proširenja pageinspect omogućuje vam dobivanje informacija o pokazivačima i verzijama redaka:

=> 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č gomila u PostgreSQL-u odnosi na tablice. Ovo je još jedna čudna upotreba izraza - hrpa je poznata struktura podataka, koji nema ništa zajedničko sa stolom. Ovdje se riječ koristi u značenju "sve je skupljeno", za razliku od uređenih indeksa.

Funkcija prikazuje podatke "kakve 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 što smo učinili:

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

Što vidimo? Kada umetnete redak, indeksni broj 1 pojavit će se na stranici tablice, pokazujući na prvu i jedinu verziju retka.

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

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

Polje xmax ispunjeno je lažnim brojem 0 jer ova verzija retka nije izbrisana i trenutna je. Transakcije neće obraćati pažnju na ovaj broj jer je postavljen bit xmax_aborted.

Poduzmimo još jedan korak prema poboljšanju čitljivosti dodavanjem informacijskih bitova brojevima transakcija. I stvorimo funkciju, budući da ć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 puno 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 row)

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

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

fiksacija

Ako je transakcija uspješno dovršena, morate zapamtiti njezin status - imajte na umu da je predana. Da biste to učinili, koristi se struktura nazvana XACT (a prije verzije 10 zvala se CLOG (dnevnik predaje) i taj se naziv još uvijek može pronaći na različitim mjestima).

XACT nije kataloška tablica sustava; ovo su datoteke u direktoriju PGDATA/pg_xact. Imaju dva bita za svaku transakciju: izvršenu i prekinutu - baš kao u zaglavlju verzije retka. Ove su informacije podijeljene u nekoliko datoteka isključivo radi praktičnosti; vratit ćemo se na ovo pitanje kada budemo razmatrali zamrzavanje. A rad s tim datotekama odvija se stranicu po stranicu, kao i sa svim ostalima.

Dakle, kada je transakcija predana u XACT-u, privrženi bit je postavljen za ovu transakciju. I to je sve što se događa tijekom predaje (iako još ne govorimo o zapisniku prije snimanja).

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

  1. Je li xmin transakcija dovršena? Ako nije, stvorena verzija niza ne bi trebala biti vidljiva.
    Ova se provjera izvodi gledanjem druge strukture, koja se nalazi u zajedničkoj memoriji instance i zove se ProcArray. Sadrži popis svih aktivnih procesa, a za svaki je naveden broj njegove trenutne (aktivne) transakcije.
  2. Ako je dovršen, kako onda - preuzimanjem obveze ili otkazivanjem? Ako je otkazano, ni verzija retka ne bi trebala biti vidljiva.
    Upravo tome služi XACT. No, iako su posljednje stranice XACT-a pohranjene u međuspremnicima u RAM-u, još uvijek je skupo provjeravati XACT svaki put. Stoga, nakon što se utvrdi status transakcije, on se zapisuje u bitove xmin_committed i xmin_aborted verzije niza. 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 samom transakcijom koja vrši umetanje? Kada se umetanje dogodi, transakcija još ne zna hoće li uspjeti. I u trenutku počinjenja više nije jasno koji su redovi u kojim stranicama promijenjeni. Takvih stranica može biti mnogo, a njihovo pamćenje je neisplativo. Osim toga, neke stranice mogu biti izbačene iz međuspremnika na disk; njihovo ponovno čitanje za promjenu bitova značajno bi usporilo predaju.

Loša strana uštede je da nakon promjena, svaka transakcija (čak i ona koja izvodi jednostavno čitanje - SELECT) može početi mijenjati podatkovne stranice u međuspremniku.

Dakle, popravimo 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 row)

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

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

Uklanjanje

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

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

Idemo izbrisati liniju.

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

Vidimo da je broj transakcije zapisan u polju xmax, 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)

Poništenje

Poništavanje promjena funkcionira slično kao i predaja, samo što je u XACT-u poništeni bit postavljen za transakciju. Poništavanje je jednako brzo kao i preuzimanje. Iako se naredba zove ROLLBACK, promjene se ne vraćaju unazad: sve što je transakcija uspjela promijeniti na podatkovnim stranicama ostaje nepromijenjeno.

=> 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 se provjeriti i xmax_aborted hint bit će biti postavljen na verziju retka. Sam xmax broj ostaje na stranici, ali ga nitko 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žurirati

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

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

Upit daje 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 polju xmax. Štoviše, ova se vrijednost ispisuje preko stare jer je prethodna transakcija otkazana. A bit xmax_aborted se briše jer status trenutne transakcije još nije poznat.

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

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

Kao i kod brisanja, vrijednost xmax u prvoj verziji retka je pokazatelj da je red zaključan.

Pa, dovršimo transakciju.

=> COMMIT;

Indeksi

Do sada smo govorili samo o stranicama tablica. Što se događa unutar indeksa?

Informacije na stranicama indeksa uvelike se razlikuju ovisno o specifičnoj vrsti indeksa. Čak i jedna vrsta indeksa ima različite vrste stranica. Na primjer, B-stablo ima stranicu s metapodacima i "obične" stranice.

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

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

Najvažnija točka je da ne postoje verzije redaka u indeksima bilo koje vrste. Pa, ili možemo pretpostaviti da je svaki redak predstavljen točno jednom verzijom. Drugim riječima, u zaglavlju retka indeksa nema polja xmin i xmax. Možemo pretpostaviti da poveznice iz indeksa vode do svih tabličnih verzija redaka - tako da možete shvatiti koju će verziju transakcija vidjeti samo gledajući tablicu. (Kao i uvijek, ovo nije cijela istina. U nekim slučajevima mapa vidljivosti može optimizirati proces, ali to ćemo detaljnije pogledati kasnije.)

Istodobno, na indeksnoj stranici nalazimo smjernice na obje verzije, i na sadašnju i na staru:

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

Virtualne transakcije

U praksi, PostgreSQL koristi optimizacije koje mu omogućuju "spremanje" brojeva transakcija.

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

Izdavanje ovog broja ne zahtijeva sinkronizaciju između svih procesa i stoga je vrlo brzo. S još jednim razlogom korištenja virtualnih brojeva upoznat ćemo se kada govorimo o zamrzavanju.

Virtualni brojevi ni na koji se način ne uzimaju u obzir u snimkama podataka.

U različitim trenucima u sustavu mogu postojati virtualne transakcije s brojevima koji su već korišteni, i to je normalno. Ali takav se broj ne može zapisati u podatkovne stranice jer sljedeći put kad 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, dobiva stvarni, 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 spremite bodove (savepoint), koji vam omogućuju otkazivanje dijela transakcije bez potpunog prekida. Ali to se ne uklapa u gornji dijagram, budući da transakcija ima isti status za sve svoje promjene, a fizički se podaci ne vraćaju.

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

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

Informacije o ugniježđenju transakcija pohranjene su u datotekama u direktoriju PGDATA/pg_subtrans. Datotekama se pristupa kroz međuspremnike u zajedničkoj memoriji instance, organizirane na isti način kao međuspremnici XACT.

Nemojte brkati ugniježđene transakcije s autonomnim transakcijama. Autonomne transakcije ni na koji način ne ovise jedna o drugoj, ali ugniježđene transakcije ovise. Nema autonomnih transakcija u običnom PostgreSQL-u, i možda je najbolje: potrebne su vrlo, vrlo rijetko, a njihova prisutnost u drugim DBMS-ovima izaziva zlouporabu, od koje onda svi pate.

Očistimo tablicu, započnimo transakciju i umetnimo 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 stavimo točku spremanja i ubacimo još jedan redak.

=> 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 točku spremanja i ubacimo treći redak.

=> 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 redak koji je dodala otkazana ugniježđena transakcija.

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, odnosno ne možete započeti novu transakciju bez dovršetka trenutne. Ovaj mehanizam se implicitno aktivira kada se koriste točke spremanja, kao i kada se rukuje PL/pgSQL iznimkama 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

Pogreške i atomičnost operacija

Što se događa ako dođe do pogreške tijekom 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 pogreške. Sada se transakcija smatra prekinutom i u njoj nisu dopuštene nikakve operacije:

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

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

=> COMMIT;
ROLLBACK

Zašto se transakcija ne može nastaviti nakon neuspjeha? Činjenica je da bi mogla nastati pogreška na način da bismo dobili pristup dijelu promjena - bila bi narušena atomičnost čak ne transakcije, već operatora. Kao u našem primjeru, gdje je operater uspio ažurirati jedan redak prije pogreš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 dopušta nastavak transakcije nakon neuspjeha kao da su radnje 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 pogoditi da u ovom načinu rada psql zapravo stavlja implicitnu točku spremanja prije svake naredbe, au slučaju neuspjeha pokreće vraćanje na nju. Ovaj se način rada ne koristi prema zadanim postavkama, jer postavljanje točaka spremanja (čak i bez vraćanja na njih) uključuje značajne troškove.

Nastavak.

Izvor: www.habr.com

Dodajte komentar