MVCC-3. Stringi versioonid

Niisiis, oleme kaalunud seotud küsimusi isolatsioon, ja tegi umbes taganemise andmete korraldamine madalal tasemel. Ja lõpuks jõudsime kõige huvitavama osani – stringversioonideni.

Pealkiri

Nagu me juba ütlesime, võib iga rida andmebaasis korraga eksisteerida mitmes versioonis. Üks versioon tuleb kuidagi teisest eristada.Selleks on igal versioonil kaks tähist, mis määravad selle versiooni toimimisaja (xmin ja xmax). Jutumärkides – sest ei kasutata mitte aega kui sellist, vaid spetsiaalset suurenevat loendurit. Ja see loendur on tehingu number.

(Tavaliselt on tegelikkus keerulisem: tehingunumber ei saa loenduri piiratud bitimahu tõttu kogu aeg kasvada. Külmutamise ajal vaatame neid detaile aga täpsemalt.)

Rea loomisel määratakse xmin tehingunumbriks, mis andis käsu INSERT, ja xmax jäetakse tühjaks.

Kui rida kustutatakse, märgitakse praeguse versiooni xmax väärtus selle tehingu numbriga, mis sooritas DELETE.

Kui rida muudetakse käsuga UPDATE, tehakse tegelikult kaks toimingut: DELETE ja INSERT. Rea praegune versioon määrab xmax võrdseks UPDATE sooritanud tehingu arvuga. Seejärel luuakse samast stringist uus versioon; selle xmin väärtus langeb kokku eelmise versiooni xmax väärtusega.

Väljad xmin ja xmax sisalduvad rea versiooni päises. Lisaks nendele väljadele sisaldab päis ka teisi, näiteks:

  • infomask on bittide jada, mis määratlevad selle versiooni omadused. Neid on päris palju; Järk-järgult kaalume peamisi.
  • ctid on link sama rea ​​järgmisele, uuemale versioonile. Stringi uusima ja värskeima versiooni puhul viitab ctid sellele versioonile endale. Arv on kujul (x,y), kus x on lehekülje number, y on massiivi indeksi number.
  • null bitmap – märgib antud versiooni veerud, mis sisaldavad nullväärtust (NULL). NULL ei kuulu tavaliste andmetüübi väärtuste hulka, seega tuleb atribuut eraldi salvestada.

Selle tulemusena on päis üsna suur - vähemalt 23 baiti iga rea ​​versiooni kohta ja tavaliselt rohkem tänu NULL-bitikaardile. Kui tabel on "kitsas" (st sisaldab vähe veerge), võivad üldkulud võtta rohkem kui kasulikku teavet.

sisestada

Vaatame lähemalt, kuidas madala taseme stringoperatsioone tehakse, alustades sisestamisest.

Katsete jaoks loome uue tabeli kahe veeru ja ühe neist indeksiga:

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

Sisestame pärast tehingu alustamist ühe rea.

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

Siin on meie praegune tehingunumber:

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

Vaatame lehe sisu. Laienduse Pageinspect funktsioon heap_page_items võimaldab teil saada teavet osutite ja reaversioonide kohta:

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

Pange tähele, et PostgreSQL-i sõna hunnik viitab tabelitele. See on veel üks kummaline termin kasutus – hunnik on teada andmestruktuur, millel pole tabeliga midagi ühist. Siin kasutatakse seda sõna tähenduses "kõik on kokku visatud", erinevalt järjestatud indeksitest.

Funktsioon näitab andmeid "nagu on" raskesti mõistetavas vormingus. Selle väljaselgitamiseks jätame ainult osa teabest ja dešifreerime selle:

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

Siin on see, mida me tegime:

  • Indeksinumbrile lisati null, et see näeks välja sama, mis t_ctid: (lehekülje number, indeksi number).
  • Dešifreeris osuti lp_flags oleku. Siin on see "tavaline" - see tähendab, et kursor viitab tegelikult stringi versioonile. Vaatame hiljem teisi tähendusi.
  • Kõigist teabebittidest on seni tuvastatud vaid kaks paari. Bitid xmin_committed ja xmin_aborted näitavad, kas tehingu number xmin on tehtud (katkestatud). Kaks sarnast bitti viitavad tehingunumbrile xmax.

Mida me näeme? Kui sisestate rea, ilmub tabeli lehele indeks number 1, mis osutab rea esimesele ja ainsale versioonile.

Stringi versioonis täidetakse xmin väli praeguse tehingunumbriga. Tehing on endiselt aktiivne, seega pole nii bitte xmin_committed kui ka xmin_aborted määratud.

Rea versiooni ctid väli viitab samale reale. See tähendab, et uuemat versiooni pole olemas.

Väli xmax on täidetud näiva numbriga 0, kuna seda rea ​​versiooni ei ole kustutatud ja see on praegune. Tehingud ei pööra sellele numbrile tähelepanu, kuna bitt xmax_aborted on määratud.

Astume veel ühe sammu loetavuse parandamise suunas, lisades tehingunumbritele infobitte. Ja loome funktsiooni, kuna vajame taotlust rohkem kui üks kord:

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

Sellisel kujul on palju selgem, mis toimub reaversiooni päises:

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

Sarnast, kuid oluliselt vähem üksikasjalikku teavet saab pseudoveergude xmin ja xmax abil tabelist endast:

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

Fikseerimine

Kui tehing on edukalt lõpule viidud, peate selle olekut meeles pidama – pange tähele, et see on tehtud. Selleks kasutatakse struktuuri nimega XACT (ja enne versiooni 10 kandis see nime CLOG (commit log) ja seda nime võib siiani erinevatest kohtadest leida).

XACT ei ole süsteemikataloogitabel; need on failid kataloogis PGDATA/pg_xact. Neil on iga tehingu jaoks kaks bitti: tehtud ja katkestatud – täpselt nagu rea versiooni päises. See teave on mugavuse huvides jagatud mitmeks failiks; naaseme selle probleemi juurde, kui kaalume külmutamist. Ja tööd nende failidega tehakse lehekülgede kaupa, nagu kõigi teistegi puhul.

Seega, kui tehing on XACT-is kinnistatud, määratakse selle tehingu jaoks siduv bitt. Ja see on kõik, mis toimub sidumise ajal (kuigi me ei räägi veel eelsalvestamise logist).

Kui mõni muu tehing avab äsja vaadatud tabelilehe, peab see vastama mitmele küsimusele.

  1. Kas xmin-tehing on lõpule viidud? Kui ei, siis ei tohiks stringi loodud versioon olla nähtav.
    See kontroll viiakse läbi, vaadates teist struktuuri, mis asub eksemplari ühismälus ja mida nimetatakse ProcArrayks. See sisaldab kõigi aktiivsete protsesside loendit ja igaühe jaoks on näidatud selle praeguse (aktiivse) tehingu number.
  2. Kui valmis, siis kuidas – kas pühendudes või tühistades? Tühistamise korral ei tohiks ka rea ​​versioon olla nähtav.
    See on täpselt see, mille jaoks XACT on mõeldud. Kuid kuigi XACT-i viimased lehed salvestatakse RAM-i puhvritesse, on XACT-i iga kord siiski kallis kontrollida. Seega, kui tehingu olek on kindlaks määratud, kirjutatakse see stringiversiooni bitidesse xmin_committed ja xmin_aborted. Kui üks neist bittidest on seatud, loetakse tehingu olek xmin teada ja järgmine tehing ei pea XACT-i juurde pääsema.

Miks ei ole need bitid määratud tehingu enda poolt, kes sisestab? Kui sisestamine toimub, ei tea tehing veel, kas see õnnestub. Ja pühendumise hetkel pole enam selge, mis ridadel millistel lehtedel muudeti. Selliseid lehti võib olla palju ja nende päheõppimine on kahjumlik. Lisaks saab mõned lehed puhvri vahemälust kettale välja tõsta; nende uuesti lugemine, et bitte muuta, aeglustaks sidumist oluliselt.

Kokkuhoiu negatiivne külg on see, et pärast muudatusi võib iga tehing (isegi lihtsa lugemise sooritamisel – SELECT) hakata muutma puhvri vahemälu andmelehti.

Niisiis, parandame muudatuse.

=> COMMIT;

Lehel pole midagi muutunud (kuid teame, et tehingu olek on XACT-is juba salvestatud):

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

Nüüd peab lehele esimesena sisenev tehing määrama xmin-tehingu oleku ja kirjutama selle teabebittidesse:

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

Kustutamine

Kui rida kustutatakse, kirjutatakse praeguse kustutamistehingu number praeguse versiooni xmax väljale ja bitt xmax_aborted tühjendatakse.

Pange tähele, et aktiivsele tehingule vastav xmax seatud väärtus toimib realukuna. Kui mõni muu tehing soovib seda rida värskendada või kustutada, on see sunnitud ootama tehingu xmax lõpuleviimist. Blokeerimisest räägime hiljem. Praegu märgime lihtsalt, et ridade lukkude arv on piiramatu. Need ei võta RAM-is ruumi ja süsteemi jõudlus ei kannata nende arvu tõttu. Tõsi, "pikkadel" tehingutel on ka muid puudusi, kuid sellest hiljem.

Kustutame rea.

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

Näeme, et tehingu number on kirjutatud väljale xmax, kuid infobitid pole määratud:

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

Tühistamine

Muudatuste katkestamine toimib sarnaselt sidumisega, ainult XACT-s on tehingu jaoks määratud katkestatud bitt. Tagasivõtmine on sama kiire kui pühendumine. Kuigi käsu nimi on ROLLBACK, muudatusi tagasi ei pöörata: kõik, mida tehinguga õnnestus andmelehtedel muuta, jääb muutumatuks.

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

Leheküljele juurdepääsul kontrollitakse olekut ja vihjebitt xmax_aborted seatakse reaversioonile. Xmax number ise jääb lehele, aga keegi ei vaata seda.

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

Ajakohastama

Värskendus toimib nii, nagu kustutaks see esmalt rea praeguse versiooni ja seejärel sisestaks uue.

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

Päring loob ühe rea (uus versioon):

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

Kuid lehel näeme mõlemat versiooni:

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

Kustutatud versioon on märgitud praeguse tehingunumbriga xmax väljale. Pealegi kirjutatakse see väärtus üle vana, kuna eelmine tehing tühistati. Ja bitt xmax_aborted kustutatakse, kuna praeguse tehingu olek pole veel teada.

Rea esimene versioon viitab nüüd teisele (väli t_ctid) kui uuemale versioonile.

Registrilehel kuvatakse teine ​​register ja teine ​​rida viitab tabelilehe teisele versioonile.

Nii nagu kustutamise puhul, näitab rea esimese versiooni xmax väärtus, et rida on lukustatud.

Noh, teeme tehingu lõpule.

=> COMMIT;

Indeksid

Siiani oleme rääkinud ainult tabelilehtedest. Mis toimub indeksite sees?

Registrilehtedel olev teave varieerub suuresti sõltuvalt konkreetsest registri tüübist. Ja isegi üht tüüpi registril on erinevat tüüpi lehti. Näiteks B-puul on metaandmete leht ja "tavalised" lehed.

Tavaliselt on lehel aga ridade ja ridade endi osutajate massiiv (nagu tabelileht). Lisaks on lehe lõpus ruumi eriandmete jaoks.

Indeksite ridadel võib olenevalt indeksi tüübist olla ka väga erinev struktuur. Näiteks B-puu puhul sisaldavad lehelehtedega seotud read indekseerimisvõtme väärtust ja viidet (ctid) vastavale tabelireale. Üldiselt saab indeksi üles ehitada hoopis teistmoodi.

Kõige tähtsam on see, et ühtegi tüüpi indeksites pole reaversioone. Noh, või võime eeldada, et iga rida esindab täpselt üks versioon. Teisisõnu, indeksirea päises ei ole xmin ja xmax välju. Võime eeldada, et indeksi lingid viivad ridade kõikidele tabeliversioonidele – nii saate aru saada, millist versiooni tehing näeb, ainult tabelit vaadates. (Nagu alati, pole see kogu tõde. Mõnel juhul võib nähtavuskaart protsessi optimeerida, kuid me vaatame seda hiljem üksikasjalikumalt.)

Samal ajal leiame registrilehelt viiteid mõlemale versioonile, nii praegusele kui ka vanale:

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

Virtuaalsed tehingud

Praktikas kasutab PostgreSQL optimeerimisi, mis võimaldavad tehingunumbreid "salvestada".

Kui tehing loeb ainult andmeid, ei mõjuta see reaversioonide nähtavust. Seetõttu väljastab teenindusprotsess tehingule esmalt virtuaalse XID-i. Number koosneb protsessi ID-st ja järjenumbrist.

Selle numbri väljastamine ei nõua kõigi protsesside vahelist sünkroonimist ja on seetõttu väga kiire. Tutvume veel ühe virtuaalnumbrite kasutamise põhjusega, kui räägime külmutamisest.

Virtuaalseid numbreid ei võeta andmete hetktõmmistes mitte kuidagi arvesse.

Erinevatel ajahetkedel võib süsteemis esineda virtuaalseid tehinguid juba kasutatud numbritega ja see on normaalne. Kuid sellist numbrit ei saa andmelehtedele kirjutada, sest järgmisel lehel võib see mõtte kaotada.

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

Kui tehing hakkab andmeid muutma, antakse sellele tegelik, kordumatu tehingunumber.

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

=> COMMIT;

Pesastatud tehingud

Salvestage punkte

Määratletud SQL-is säästa punkte (savepoint), mis võimaldavad teil osa tehingust tühistada ilma seda täielikult katkestamata. Kuid see ei mahu ülaltoodud diagrammi, kuna tehingul on kõigi selle muudatuste jaoks sama olek ja füüsiliselt andmeid tagasi ei pöörata.

Selle funktsiooni rakendamiseks jagatakse salvestuspunktiga tehing mitmeks eraldiseisvaks osaks pesastatud tehingud (alltehing), mille olekut saab eraldi hallata.

Pesastatud tehingutel on oma number (suurem kui põhitehingu number). Pesastatud tehingute olek salvestatakse XACT-is tavapärasel viisil, kuid lõplik staatus sõltub põhitehingu olekust: kui see tühistatakse, siis tühistatakse ka kõik pesastatud tehingud.

Teave tehingute pesastamise kohta salvestatakse failidesse kataloogis PGDATA/pg_subtrans. Failidele pääseb juurde eksemplari ühismälus olevate puhvrite kaudu, mis on korraldatud samamoodi nagu XACT puhvrid.

Ärge ajage segamini pesastatud tehinguid autonoomsete tehingutega. Autonoomsed tehingud ei sõltu üksteisest kuidagi, küll aga pesastatud tehingud. Tavalises PostgreSQL-is autonoomseid tehinguid pole ja võib-olla parimal juhul: neid läheb vaja väga-väga harva ning nende olemasolu teistes DBMS-ides kutsub esile kuritarvitamise, mille all kannatavad kõik.

Tühjendame tabeli, alustame tehingut ja sisestame rea:

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

Nüüd paneme salvestuspunkti ja sisestame teise rea.

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

Pange tähele, et funktsioon txid_current() tagastab peamise tehingunumbri, mitte pesastatud tehingunumbri.

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

Kerime tagasi salvestuspunkti ja sisestame kolmanda rea.

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

Lehel näeme jätkuvalt tühistatud pesastatud tehingu lisatud rida.

Parandame muudatused.

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

Nüüd näete selgelt, et igal pesastatud tehingul on oma olek.

Pange tähele, et pesastatud tehinguid ei saa SQL-is otseselt kasutada, see tähendab, et te ei saa alustada uut tehingut ilma praegust tehingut lõpetamata. See mehhanism aktiveeritakse kaudselt nii salvestuspunktide kasutamisel kui ka PL/pgSQL erandite käsitlemisel ja paljudel muudel eksootilisematel juhtudel.

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

Vead ja operatsioonide atomaalsus

Mis juhtub, kui toimingu sooritamisel ilmneb tõrge? Näiteks nii:

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

Tekkis viga. Nüüd loetakse tehing katkestatuks ja selles pole lubatud ühtegi toimingut:

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

Ja isegi kui proovite muudatusi sisse viia, teatab PostgreSQL katkestamisest:

=> COMMIT;
ROLLBACK

Miks ei saa tehingut pärast ebaõnnestumist jätkata? Fakt on see, et viga võib tekkida nii, et pääseksime ligi osale muudatustest – rikutaks isegi mitte tehingu, vaid operaatori tuumasust. Nagu meie näites, kus operaatoril õnnestus üks rida enne viga värskendada:

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

Peab ütlema, et psql-l on režiim, mis lubab siiski pärast ebaõnnestumist tehingut jätkata, nagu oleks vigase operaatori tegevus tagasi keeratud.

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

Pole raske arvata, et selles režiimis paneb psql tegelikult iga käsu ette kaudse salvestuspunkti ja tõrke korral algatab selle tagasipööramise. Seda režiimi vaikimisi ei kasutata, kuna salvestuspunktide määramine (isegi ilma nende juurde tagasi pöördumata) hõlmab märkimisväärseid üldkulusid.

Jätkamine.

Allikas: www.habr.com

Lisa kommentaar