MVCC-3. String változatok

Tehát megvizsgáltuk a kapcsolódó kérdéseket szigetelés, és visszavonult kb az adatok alacsony szintű rendszerezése. És végül elérkeztünk a legérdekesebb részhez - a húros változatokhoz.

Cím

Mint már említettük, minden sor egyidejűleg több változatban is létezhet az adatbázisban. Az egyik verziót meg kell különböztetni a másiktól, ezért mindegyik verzión két jelzés található, amelyek meghatározzák a verzió működési idejét (xmin és xmax). Idézőjelben - mert nem az időt, mint olyat használják, hanem egy speciális növekvő számlálót. És ez a számláló a tranzakció száma.

(Szokás szerint a valóság ennél bonyolultabb: a tranzakciószám a számláló korlátozott bitkapacitása miatt nem tud állandóan növekedni. De ezeket a részleteket akkor nézzük meg részletesen, ha a lefagyásig eljutunk.)

Egy sor létrehozásakor az xmin az INSERT parancsot kiadó tranzakciószámra lesz állítva, az xmax pedig üresen marad.

Egy sor törlésekor az aktuális verzió xmax értéke a TÖRLÉST végrehajtó tranzakció számával lesz megjelölve.

Ha egy sort módosít egy UPDATE paranccsal, akkor valójában két műveletet hajtanak végre: DELETE és INSERT. A sor jelenlegi verziója az xmax értéket egyenlő a FRISSÍTÉST végrehajtó tranzakció számával. Ezután ugyanannak a karakterláncnak egy új verziója jön létre; az xmin értéke egybeesik az előző verzió xmax értékével.

Az xmin és xmax mezőket a sorverzió fejléce tartalmazza. Ezeken a mezőkön kívül a fejléc másokat is tartalmaz, például:

  • Az infomask egy bitsorozat, amely meghatározza ennek a verziónak a tulajdonságait. Elég sok van belőlük; Fokozatosan megvizsgáljuk a főbbeket.
  • A ctid egy hivatkozás ugyanannak a sornak a következő, újabb verziójára. Egy sor legújabb, legfrissebb verziója esetében a ctid magára a verzióra utal. A szám alakja (x,y), ahol x az oldalszám, y az indexszám a tömbben.
  • null bitmap – Megjelöli az adott verzió azon oszlopait, amelyek null értéket (NULL) tartalmaznak. A NULL nem tartozik a normál adattípus értékek közé, ezért az attribútumot külön kell tárolni.

Ennek eredményeként a fejléc meglehetősen nagy - legalább 23 bájt a sor minden verziójához, és általában több a NULL bitkép miatt. Ha a táblázat "keskeny" (azaz kevés oszlopot tartalmaz), akkor a többletköltség többet foglalhat el, mint a hasznos információ.

helyezze

Nézzük meg közelebbről az alacsony szintű karakterlánc-műveletek végrehajtását, kezdve a beszúrással.

Kísérletekhez hozzunk létre egy új táblázatot két oszloppal és az egyiken indexel:

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

Szúrjunk be egy sort a tranzakció indítása után.

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

Íme az aktuális tranzakciószámunk:

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

Nézzük az oldal tartalmát. A pageinspect bővítmény heap_page_items függvénye lehetővé teszi, hogy információkat szerezzen a mutatókkal és a sorverziókkal kapcsolatban:

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

Vegye figyelembe, hogy a PostgreSQL-ben a kupac szó táblákra utal. Ez a kifejezés másik furcsa használata – egy kupac ismert adatszerkezet, aminek semmi köze a táblázathoz. Itt a szót a „minden össze van dobva” értelemben használjuk, szemben a rendezett indexekkel.

A függvény az adatokat „ahogy vannak”, nehezen érthető formátumban jeleníti meg. Ennek kiderítéséhez az információnak csak egy részét hagyjuk meg, és megfejtjük:

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

Íme, mit tettünk:

  • Hozzáadott egy nullát az indexszámhoz, hogy úgy nézzen ki, mint a t_ctid: (oldalszám, indexszám).
  • Megfejtette az lp_flags mutató állapotát. Itt ez "normál" - ez azt jelenti, hogy a mutató valójában a karakterlánc verziójára utal. A többi jelentéssel később foglalkozunk.
  • Az összes információs bitből eddig csak két párt azonosítottak. Az xmin_committed és xmin_aborted bitek jelzik, hogy az xmin számú tranzakció véglegesítve (megszakítva) van-e. Két hasonló bit utal az xmax tranzakciószámra.

Mit látunk? Amikor beszúr egy sort, egy 1-es indexszám jelenik meg a táblázat oldalán, amely a sor első és egyetlen változatára mutat.

A karakterlánc verzióban az xmin mező az aktuális tranzakciószámmal van kitöltve. A tranzakció továbbra is aktív, így az xmin_committed és az xmin_aborted bitek sincsenek beállítva.

A sorverzió ctid mező ugyanarra a sorra vonatkozik. Ez azt jelenti, hogy nem létezik újabb verzió.

Az xmax mező 0-s fiktív számmal van kitöltve, mert a sornak ez a verziója nem lett törölve és aktuális. A tranzakciók nem figyelnek erre a számra, mert az xmax_aborted bit be van állítva.

Tegyünk még egy lépést az olvashatóság javítása felé azáltal, hogy információs biteket adunk a tranzakciószámokhoz. És hozzunk létre egy függvényt, mivel többször is szükségünk lesz a kérésre:

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

Ebben a formában sokkal világosabb, hogy mi történik a sorváltozat fejlécében:

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

Hasonló, de lényegesen kevésbé részletezett információk nyerhetők magából a táblázatból, az xmin és xmax pszeudooszlopok használatával:

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

Rögzítés

Ha egy tranzakció sikeresen befejeződött, emlékeznie kell az állapotára – vegye figyelembe, hogy lekötött. Ehhez egy XACT nevű struktúrát használnak (a 10-es verzió előtt pedig CLOG (commit log) volt, és ez a név még mindig megtalálható különböző helyeken).

Az XACT nem egy rendszerkatalógus-tábla; ezek a fájlok a PGDATA/pg_xact könyvtárban. Minden tranzakcióhoz két bit tartozik: véglegesített és megszakított – csakúgy, mint a sor verzió fejlécében. Ez az információ kizárólag a kényelem kedvéért több fájlra van felosztva; erre a problémára visszatérünk, amikor a lefagyasztást fontolgatjuk. És ezekkel a fájlokkal a többihez hasonlóan oldalról oldalra kell dolgozni.

Tehát, amikor egy tranzakciót XACT-ben véglegesítenek, a véglegesített bit be van állítva ehhez a tranzakcióhoz. És ez minden, ami a commiting során történik (bár az előfelvételi naplóról még nem beszélünk).

Amikor egy másik tranzakció hozzáfér az imént megtekintett táblázatoldalhoz, számos kérdésre kell válaszolnia.

  1. Az xmin tranzakció befejeződött? Ha nem, akkor a karakterlánc létrehozott verziója nem lehet látható.
    Ez az ellenőrzés egy másik struktúra megtekintésével történik, amely a példány megosztott memóriájában található, és a neve ProcArray. Tartalmazza az összes aktív folyamat listáját, és mindegyiknél megjelenik az aktuális (aktív) tranzakció száma.
  2. Ha elkészült, akkor hogyan – kötelezettségvállalással vagy lemondással? Ha megszakítja, akkor a soros verzió sem lehet látható.
    Az XACT pontosan erre való. De bár az XACT utolsó oldalai a RAM puffereiben vannak tárolva, még mindig költséges az XACT minden egyes alkalommal történő ellenőrzése. Ezért a tranzakció állapotának meghatározása után a karakterlánc-verzió xmin_committed és xmin_aborted bitjébe íródik. Ha ezen bitek egyike be van állítva, akkor az xmin tranzakció állapota ismertnek tekinthető, és a következő tranzakciónak nem kell hozzáférnie az XACT-hoz.

Miért nem maga a tranzakció állítja be ezeket a biteket a beillesztés során? Amikor beszúrás történik, a tranzakció még nem tudja, hogy sikeres lesz-e. Az elköteleződés pillanatában pedig már nem derül ki, hogy melyik soron mely oldalakat változtatták meg. Nagyon sok ilyen oldal lehet, és ezek memorizálása veszteséges. Ezenkívül néhány oldalt a puffer-gyorsítótárból a lemezre lehet eltávolítani; a bitek újbóli beolvasása jelentősen lelassítaná a véglegesítést.

A megtakarítás hátránya, hogy a változtatások után bármely tranzakció (még egy egyszerű olvasást is végrehajtó - SELECT) megkezdheti a puffer-gyorsítótár adatlapjainak módosítását.

Tehát javítsuk a változást.

=> COMMIT;

Semmi sem változott az oldalon (de tudjuk, hogy a tranzakció állapota már rögzítve van az XACT-ban):

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

Most az oldalt először elérő tranzakciónak meg kell határoznia az xmin tranzakció állapotát, és be kell írnia az információs bitekre:

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

Eltávolítás

Egy sor törlésekor az aktuális törlési tranzakció száma az aktuális verzió xmax mezőjébe kerül, és az xmax_aborted bit törlődik.

Vegye figyelembe, hogy az aktív tranzakciónak megfelelő xmax beállított értéke sorzárként működik. Ha egy másik tranzakció szeretné frissíteni vagy törölni ezt a sort, akkor kénytelen lesz megvárni az xmax tranzakció befejezését. A blokkolásról később bővebben fogunk beszélni. Egyelőre csak megjegyezzük, hogy a sorzárak száma korlátlan. Nem foglalnak helyet a RAM-ban, és a rendszer teljesítménye nem szenved a számuktól. Igaz, a „hosszú” tranzakcióknak más hátrányai is vannak, de erről később.

Töröljük a sort.

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

Azt látjuk, hogy a tranzakciószám be van írva az xmax mezőbe, de az információs bitek nincsenek beállítva:

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

Törlés

A változtatások megszakítása a véglegesítéshez hasonlóan működik, csak az XACT-ban a megszakított bit van beállítva a tranzakcióhoz. A visszavonás olyan gyors, mint az elköteleződés. Bár a parancs neve ROLLBACK, a változtatások nem kerülnek visszaállításra: minden, amit a tranzakciónak sikerült megváltoztatnia az adatlapokon, változatlan marad.

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

Az oldal megnyitásakor a rendszer ellenőrzi az állapotot, és az xmax_aborted tippbitet a sorverzióra állítja. Maga az xmax szám az oldalon marad, de senki nem fogja megnézni.

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

frissítés

A frissítés úgy működik, mintha először törölte volna a sor aktuális verzióját, majd egy újat szúrna be.

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

A lekérdezés egy sort hoz létre (új verzió):

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

De az oldalon mindkét verziót látjuk:

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

A törölt verzió az aktuális tranzakciószámmal van jelölve az xmax mezőben. Ezenkívül ez az érték a régi fölé kerül, mivel az előző tranzakciót törölték. És az xmax_aborted bit törlődik, mert az aktuális tranzakció állapota még nem ismert.

A sor első verziója most a másodikra ​​(t_ctid mező) hivatkozik, mint az újabbra.

Egy második index jelenik meg az indexoldalon, egy második sor pedig a táblázatoldal második verziójára hivatkozik.

Csakúgy, mint a törlésnél, a sor első verziójában szereplő xmax érték azt jelzi, hogy a sor zárolva van.

Nos, fejezzük be a tranzakciót.

=> COMMIT;

Indexek

Eddig csak a táblázatos oldalakról beszéltünk. Mi történik az indexeken belül?

Az indexoldalakon található információk nagymértékben változnak az adott index típusától függően. És még egy típusú indexnek is vannak különböző típusú oldalai. Például egy B-fának van egy metaadatoldala és „szokásos” oldalai.

Az oldal azonban általában tartalmaz egy sor mutatót a sorokra és magukra a sorokra (akárcsak egy táblázatoldal). Ezenkívül az oldal végén van hely speciális adatoknak.

Az indexek sorainak szerkezete is nagyon eltérő lehet az index típusától függően. Például egy B-fa esetében a levéloldalakhoz kapcsolódó sorok tartalmazzák az indexelési kulcs értékét és a megfelelő táblázatsorra mutató hivatkozást (ctid). Általánosságban elmondható, hogy az indexet teljesen más módon lehet felépíteni.

A legfontosabb pont az, hogy semmilyen típusú indexben nincsenek sorverziók. Nos, vagy feltételezhetjük, hogy minden sort pontosan egy változat képvisel. Más szóval, az indexsor fejlécében nincsenek xmin és xmax mezők. Feltételezhetjük, hogy az index hivatkozásai a sorok összes táblaverziójához vezetnek – így csak a táblázatból kitalálható, hogy melyik verziót fogja látni a tranzakció. (Mint mindig, ez nem a teljes igazság. Bizonyos esetekben a láthatósági térkép optimalizálhatja a folyamatot, de ezt később részletesebben is megvizsgáljuk.)

Ugyanakkor az index oldalon találunk mutatókat mindkét verzióra, mind a jelenlegire, mind a régire:

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

Virtuális tranzakciók

A gyakorlatban a PostgreSQL olyan optimalizálásokat használ, amelyek lehetővé teszik a tranzakciószámok „mentését”.

Ha egy tranzakció csak adatokat olvas, az nincs hatással a sorverziók láthatóságára. Ezért a szolgáltatási folyamat először virtuális xid-t ad ki a tranzakcióhoz. A szám egy folyamatazonosítóból és egy sorszámból áll.

Ennek a számnak a kiadása nem igényel szinkronizálást az összes folyamat között, ezért nagyon gyors. A virtuális számok használatának másik okával fogunk megismerkedni, amikor a lefagyásról beszélünk.

A virtuális számokat semmilyen módon nem veszik figyelembe az adatpillanatképek.

Különböző időpontokban előfordulhatnak virtuális tranzakciók a rendszerben már használt számokkal, és ez normális. De egy ilyen számot nem lehet adatoldalakba írni, mert az oldal legközelebbi elérésekor minden értelmét elveszítheti.

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

Ha egy tranzakció elkezdi megváltoztatni az adatokat, akkor valódi, egyedi tranzakciószámot kap.

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

=> COMMIT;

Beágyazott tranzakciók

Mentse el a pontokat

SQL-ben definiálva pontokat menteni (mentési pont), amelyek lehetővé teszik a tranzakció egy részének törlését anélkül, hogy teljesen megszakítanák. De ez nem fér bele a fenti diagramba, mivel a tranzakció állapota minden változásnál azonos, és fizikailag egyetlen adat sem kerül visszagörgetésre.

Ennek a funkciónak a megvalósításához egy mentési ponttal rendelkező tranzakció több különálló részre van felosztva beágyazott tranzakciók (altranzakció), melynek állapota külön kezelhető.

A beágyazott tranzakcióknak saját számuk van (magasabb, mint a fő tranzakció száma). A beágyazott tranzakciók állapota a szokásos módon rögzítésre kerül az XACT-ban, de a végső állapot a fő tranzakció állapotától függ: ha törlik, akkor minden beágyazott tranzakció is törlődik.

A tranzakciók egymásba ágyazásával kapcsolatos információk a PGDATA/pg_subtrans könyvtárban található fájlokban tárolódnak. A fájlok a példány megosztott memóriájában található puffereken keresztül érhetők el, amelyek ugyanúgy vannak rendezve, mint az XACT pufferek.

Ne keverje össze a beágyazott tranzakciókat az autonóm tranzakciókkal. Az autonóm tranzakciók semmilyen módon nem függenek egymástól, de a beágyazott tranzakciók igen. A szokásos PostgreSQL-ben nincsenek autonóm tranzakciók, és talán a legjobb: nagyon-nagyon ritkán van rájuk szükség, más DBMS-ekben való jelenlétük pedig visszaéléseket vált ki, amitől aztán mindenki szenved.

Töröljük a táblázatot, indítsuk el a tranzakciót és szúrjuk be a sort:

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

Most tegyünk egy mentési pontot és szúrjunk be egy másik sort.

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

Vegye figyelembe, hogy a txid_current() függvény a fő tranzakciószámot adja vissza, nem a beágyazott tranzakciószámot.

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

Térjünk vissza a mentési ponthoz, és illesszük be a harmadik sort.

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

Az oldalon továbbra is a törölt beágyazott tranzakció által hozzáadott sort látjuk.

Javítjuk a változtatásokat.

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

Most már jól látható, hogy minden beágyazott tranzakciónak megvan a maga állapota.

Ne feledje, hogy a beágyazott tranzakciók nem használhatók kifejezetten az SQL-ben, vagyis nem indíthat új tranzakciót az aktuális tranzakció befejezése nélkül. Ez a mechanizmus implicit módon aktiválódik mentési pontok használatakor, valamint PL/pgSQL kivételek kezelésekor és számos más, egzotikusabb esetben.

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

A műveletek hibái és atomitása

Mi történik, ha hiba történik a művelet végrehajtása közben? Például így:

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

Hiba történt. Most a tranzakció megszakítottnak minősül, és semmilyen művelet nem engedélyezett benne:

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

És még ha megpróbálja is végrehajtani a változtatásokat, a PostgreSQL megszakítást jelent:

=> COMMIT;
ROLLBACK

Miért nem folytatható a tranzakció a sikertelenség után? Tény, hogy olyan hiba léphet fel, hogy a változtatások egy részéhez hozzáférnénk - nem is a tranzakció atomitása, hanem az üzemeltető sérülne. Mint a példánkban, ahol az operátornak sikerült egy sort frissítenie a hiba előtt:

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

El kell mondanunk, hogy a psql-nek van egy olyan módja, amely továbbra is lehetővé teszi a tranzakció folytatását egy hiba után is, mintha a hibás operátor cselekedeteit visszaállítanák.

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

Nem nehéz kitalálni, hogy ebben a módban a psql valóban minden parancs elé tesz egy implicit mentési pontot, és hiba esetén elindítja a visszaállítást. Ez a mód alapértelmezés szerint nem használatos, mivel a mentési pontok beállítása (még a visszagörgetés nélkül is) jelentős többletköltséggel jár.

Folytatni kell.

Forrás: will.com

Hozzászólás