A PostgreSQL belső mechanizmusainak működése lehetővé teszi számára, hogy bizonyos helyzetekben nagyon gyors legyen, míg másokban "nem annyira".

Tehát áttekintettük a problémákat, amelyek kapcsolódnak az izolációhoz, és tettünk egy kitérőt a data alacsony szintű szervezéséről. Végül pedig eljutottunk a legérdekesebbhez — a sztring verziókhoz.

A fejléc

Mint már említettük, minden sztring egyszerre több verzióban is létezhet az adatbázisban. Az egyes verziókat meg kell különböztetni egymástól. E célból minden verziónak két jelölése van, amelyek meghatározzák a verzió érvényességi idejét (xmin és xmax). A „idő” idézőjelben van, mert nem az időt magát használjuk, hanem egy speciális növekvő számlálót. Ez a számláló — a tranzakciós szám.

(Mint általában, a valóságban minden bonyolultabb: a tranzakciós szám nem növekedhet folyamatosan a számláló korlátozott bitmélysége miatt. De ezeket a részleteket részletesebben áttekintjük, amikor elérjük a fagyasztást.)

Amikor egy sztring létrejön, az xmin értéke a végrehajtott INSERT parancs tranzakciós számára áll be, míg az xmax nem töltődik ki.

Amikor egy sztringet törölnek, a jelenlegi verzió xmax értéke a DELETE parancsot végrehajtó tranzakciós számra van jelölve.

Amikor egy sztringet az UPDATE parancs végrehajtásával módosítanak, valójában két művelet történik: DELETE és INSERT. A sztring aktuális verziójában az xmax értéke megegyezik az UPDATE-t végrehajtó tranzakciós számával. Ezután egy új verzió jön létre ugyanabból a sztringből; az xmin értéke megegyezik az előző verzió xmax értékével.

Az xmin és xmax mezők a sztring verzió fejlécébe tartoznak. Emezők mellett a fejléctartalmaz másokat is, például:

  • infomask — egy bitmolett, amely meghatározza a verzió sajátosságait. Ezekből jócskán van; a legfontosabbakat fokozatosan áttekintjük.
  • ctid — hivatkozás a következő, újabb verzióra ugyanazon sztring esetén. A legújabb, aktuális verzió ctid-je erre a verzióra utal. A szám formája (x,y), ahol x — az oldal száma, y — a mutató sorszáma a tömbben.
  • a nem definiált értékek bitkártyája — megjelöli azokat az oszlopokat az adott verzióban, amelyek nem definiált értéket tartalmaznak (NULL). A NULL nem egyike a standard adat típusoknak, ezért ezt a jellemzőt külön kell tárolni.

Ennek eredményeként a fejléc meglehetősen nagy — legalább 23 bájt minden sztring verzióhoz, és általában több, a NULL bitkártyája miatt. Ha a tábla „szűk” (azaz kevés oszlopot tartalmaz), a költségek meghaladhatják a hasznos információt.

Beszúrás

Nézzük meg közelebbről, hogyan hajtják végre az alacsony szintű műveleteket a karakterláncokkal, és kezdjünk a beszúrással.

Kísérletezésekhez hozzunk létre egy új táblát két oszloppal, és indexeljük az egyiket:

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

Beszúrunk egy sort, előtte elindítva a tranzakciót.

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

Itt van a jelenlegi tranzakciónk száma:

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

Nézzük meg az oldal tartalmát. A heap_page_items függvény a pageinspect kiterjesztésből információt ad az irányítópontok és a sorok verziói alapján:

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

Megjegyezzük, hogy a heap (halom) szó a PostgreSQL-ben táblákat jelöl. Ez egy újabb furcsa használata a terminológiának - a halom egy jól ismert adatstruktúra, amelynek semmi köze a táblához. Itt az a szó a "minden össze van halmozva" értelemben használandó, ellentétben a rendezett indexekkel.

A függvény az adatokat "ahogy vannak" mutatja be, egy nehezen érthető formátumban. Hogy megértsük, csak a lényeges információt tartjuk meg és megfejtjük azt:

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

Ez történt:

  • Hozzáadtunk egy nullát a pointer számához, hogy ugyanolyan formát kapjunk, mint a t_ctid: (lap száma, pointer száma).
  • Megfejtettük az lp_flags pointer állapotát. Itt az „normal” - ez azt jelenti, hogy a pointer valóban egy sor verziójára mutat. Más értékeket később megvizsgálunk.
  • Minden információs bitből eddig csak két párt emeltünk ki. A xmin_committed és xmin_aborted bitek megmutatják, hogy a xmin számú tranzakció rögzítve (vagy törölve) lett. Két hasonló bit a xmax számú tranzakcióra vonatkozik.

Mit is látunk? A táblázatokban a sor beszúrásakor megjelenik egy 1-es számú mutató, amely az első és egyetlen sorverzióra hivatkozik.

A sorverzió xmin mezője a jelenlegi tranzakció számával van kitöltve. A tranzakció még aktív, így mindkét bit, az xmin_committed és az xmin_aborted nincs beállítva.

A sorverzió ctid mezője ugyanarra a sorra hivatkozik. Ez azt jelenti, hogy nincs újabb verzió.

Az xmax mező 0-ás ál számot tartalmaz, mivel ez a sorverzió nincs törölve és aktuális. A tranzakciók nem fognak figyelembe venni ezt a számot, mivel az xmax_aborted bit be van állítva.

Tegyük meg a következő lépést a jobb olvashatóság érdekében, írjunk hozzá információs biteket a tranzakciók számához. És hozzunk létre egy funkciót, mivel a lekérdezésre még szükségünk lesz:

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

Ilyen formában sokkal érthetőbb, hogy mi történik a sorverzió fejlécében:

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

Hasonló, de lényegesen kevesebb részletességű információt kaphatunk a táblázatból is, az xmin és xmax álnév oszlopok használatával:

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

Rögzítés

A tranzakció sikeres befejezésekor meg kell jegyeznünk annak állapotát – jelezni, hogy rögzítve van. Erre a célra egy XACT nevű struktúrát használnak (10-es verzió előtt CLOG-nak (commit log) hívták, és ez a név még mindig találkozhat különböző helyeken).

Az XACT nem egy rendszer katalógus táblázat; ezek fájlok a PGDATA/pg_xact könyvtárban. Minden tranzakcióhoz két bitet rendelnek: committed és aborted – pontosan úgy, ahogyan a sorverzió fejlécében. Ezt az információt több fájlra osztják, kizárólag a kényelem érdekében, erről még visszatérünk, amikor a fagyasztást vizsgáljuk. És ezekkel a fájlokkal lapozva dolgoznak, mint bármely más fájl esetében.

Tehát, a tranzakció rögzítésekor az XACT-ben a committed bit beállításra kerül az adott tranzakcióhoz. És ez minden, ami a rögzítés során történik (bár egyelőre nem beszélünk a naplózásról).

Amikor egy másik tranzakció hozzáfér a táblázat oldalához, amelyet most néztünk, akkor több kérdésre kell választ adnia.

  1. Lezárult a xmin tranzakció? Ha nem, akkor a létrehozott sor verziója nem lehet látható.
    Ellenőrzés történik egy másik struktúra megtekintésével, amely a példány közös memóriájában található és ProcArray-nak hívják. Ez tartalmazza az összes aktív folyamat listáját, és minden egyes folyamat aktuális (aktív) tranzakciójának számát is.
  2. Ha lezárult, akkor hogyan — rögzítéssel vagy visszavonással? Ha visszavonással, akkor a sor verziója sem lehet látható.
    Erre valóban szükség van az XACT-ra. De mivel az utolsó XACT oldalak a memóriában tárolódnak, minden egyes alkalommal az XACT ellenőrzése költséges. Ezért egyszer már megállapított tranzakciós állapotot a sor verziójának xmin_committed és xmin_aborted bitekben rögzítik. Ha ezek közül bármelyik bit be van állítva, a xmin tranzakció állapota ismertnek számít, és a következő tranzakciónak már nem kell az XACT-hoz fordulnia.

Miért nem ezeket a biteket maga a beszúró tranzakció állítja be? Amikor a beszúrás megtörténik, a tranzakció még nem tudja, hogy sikeresen befejeződik-e. A rögzítés pillanatában már nem világos, hogy pontosan mely sorokat módosítottak, amelyeken mely oldalakon. Lehet, hogy sok ilyen oldal van, és nem gazdaságos őket nyomon követni. Ráadásul egyes oldalak kiemelhetők a pufferelt gyorsítótárról a lemezre; az újbóli olvasása a bitek módosításához jelentősen lelassítaná a rögzítést.

A megtakarítás másik oldala, hogy a módosítások után bármely tranzakció (még egy egyszerű olvasás — SELECT végrehajtása is) elkezdheti a dátumoldalak változtatását a pufferelt gyorsítótárban.

Tehát rögzítsük a módosítást.

=> COMMIT;

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

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

Most a tranzakciónak, amely először hivatkozott az oldalra, meg kell határoznia a xmin tranzakció státuszát, és ezt az információs bitekbe fogja rögzíteni:

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

Törlés

A törölt sor xmax mezője a jelenlegi törlési tranzakció számára tárolja a számot, és az xmax_aborted bit visszaállításra kerül.

Észrevesszük, hogy az aktív tranzakcióhoz tartozó xmax érték blokkolja a sort. Ha egy másik tranzakció frissíteni vagy törölni szeretné ezt a sort, akkor meg kell várnia az xmax tranzakció befejezését. A blokkolásokról bővebben később fogunk beszélni. Egyelőre csak annyit jegyzeteljünk meg, hogy a blokkolások száma korlátlan. Nem foglalnak el helyet a rendszer memóriájában, és a teljesítményük nem szenved a számuk miatt. Igaz, hogy a „hosszú” tranzakcióknak más hátrányai is vannak, de erről is később.

Töröljük a sort.

=> KEZDJÜK;
=> TÖRÖLD A t;
=> VÁLASZD KI a txid_current()-ot;
 txid_current 
--------------
         3665
(1 row)

Látjuk, hogy a tranzakció száma az xmax mezőbe került, de az információs bitek nincsenek beállítva:

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

Lemondás

A változások visszavonása hasonlóan működik, mint a rögzítés, csak az XACT-ben a tranzakcióhoz az aborted bitet állítják be. A visszavonás olyan gyorsan zajlik, mint a rögzítés. Bár a parancs neve ROLLBACK, a változások visszaállítása nem történik meg: minden, amit a tranzakció a dátum lapokon megváltoztatott, érintetlen marad.

=> ROLLBACK;
=> VÁLASZD KI a * FROM heap_page('t',0);
 ctid  | állapot  |   xmin   | xmax | t_ctid 
-------+--------+----------+------+--------
 (0,1) | normális | 3664 (c) | 3665 | (0,1)
(1 row)

Amikor a laphoz nyúlunk, ellenőrizni fogják a státuszt, és a sor verziójába az xmax_aborted bit is be fog kerülni. Az xmax szám a lapon marad, de már senki sem fog rá figyelni.

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

=> SELECT * FROM heap_page('t',0);
 ctid  | állapot  |   xmin   |   xmax   | t_ctid 
-------+--------+----------+----------+--------
 (0,1) | normális | 3664 (c) | 3665 (a) | (0,1)
(1 row)

Frissítés

A frissítés úgy működik, mintha először a jelenlegi sor verzió törlése zajlott volna, majd egy új beillesztése.

=> KEZDJÜK;
=> FRISSÍTSD a t SET s = 'BAR';
=> VÁLASZD KI a txid_current()-ot;
 txid_current 
--------------
         3666
(1 row)

A lekérdezés egy sort ad vissza (új verzió):

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

De a lapon mindkét verziót látjuk:

=> SELECT * FROM heap_page('t',0);
 ctid  | állapot  |   xmin   | xmax  | t_ctid 
-------+--------+----------+-------+--------
 (0,1) | normális | 3664 (c) | 3666  | (0,2)
 (0,2) | normális | 3666     | 0 (a) | (0,2)
(2 rows)

A törölt verzió a jelenlegi tranzakció számával van megjelölve az xmax mezőben. Ez az érték a régi fölé van írva, mivel az előző tranzakciót visszavonták. Az xmax_aborted bit vissza van állítva, mivel a jelenlegi tranzakció státusza még ismeretlen.

Az első sor verziója most a másodikra utal (t_ctid mező), mint a frissebbre.

A második mutató és a második sor megjelenik az indexoldalon, amelyek a táblázatos oldal második verziójára utalnak.

Mint a törlésnél, az első verzió sorának xmax értéke jelzi, hogy a sor zárolva van.

Most befejezzük a tranzakciót.

=> COMMIT;

Indexek

Eddig csak táblázatos oldalakról beszéltünk. Mi történik az indexek belsejében?

Az indexoldalakon található információk erősen függenek a konkrét index típusától. Még egy típusú indexen belül is különböző oldaltípusok létezhetnek. Például a B-fában van egy metainformációkkal rendelkező oldal és 'normál' oldalak.

Ennek ellenére az oldalon általában van egy sor mutatókészlet és maguk a sorok (ugyanúgy, mint a táblázatos oldalon). Ezen kívül az oldal alján helyet kapnak a speciális adatok.

Az indexekben található sorok is nagyon eltérő struktúrával rendelkezhetnek az index típusától függően. Például a B-fához tartozó, levéloldalakkal kapcsolatos sorok tartalmazzák az indexkulcs értékét és egy hivatkozást (ctid) a megfelelő táblázat sorára. Általánosságban az index más módon is felépíthető.

A legfontosabb pont az, hogy bármilyen típusú indexben nincsenek verziók a sorokban. Vagy úgy is lehet mondani, hogy minden sor pontosan egy verziót képvisel. Más szóval, az indexsor fejléce nem tartalmaz xmin és xmax mezőket. Tekinthetjük úgy, hogy az index hivatkozásai minden táblázati sor verzióra mutatnak — így csak a táblázatra nézve tudhatjuk meg, hogy a tranzakció mely verziót fogja látni. (Mint általában, ez nem teljes igazság. Bizonyos esetekben a láthatósági térkép optimalizálni tudja a folyamatot, de ezt később részletesebben megvizsgáljuk.)

Ekkor az indexoldalon mindkét verzióra, mind az aktuálisra, mind a régi verzióra hivatkozásokat találunk:

=> 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ást alkalmaz, amely lehetővé teszi a tranzakciós számok 'megtakarítását'.

Ha a tranzakció csak olvassa az adatokat, akkor nem befolyásolja a sorok verzióinak láthatóságát. Ezért a kezelő folyamat először virtuális számot (virtual xid) ad a tranzakciónak. A szám a folyamat azonosítójából és egy sorozatszámból áll.

E szám kiadása nem igényli a folyamatok közötti szinkronizálást, ezért nagyon gyorsan végrehajtható. A virtuális számok használatának másik okával ismerkedünk meg, amikor a felfüggesztésről beszélünk.

A virtuális számok nincsenek figyelembe véve az adatállapotokban.

Különböző időpontokban a rendszerben virtuális tranzakciók lehetnek olyan számokkal, amelyeket már használtak, és ez teljesen normális. De ilyen számot nem lehet az adatoldalakra felvenni, mert a következő híváskor az elveszítheti minden értelmét.

=> KEZD;
=> VÁLASZTXID_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                         
(1 sor)

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

=> FRISSÍTÉS accounts SET amount = amount - 1.00;
=> VÁLASZTXID_current_if_assigned();
 txid_current_if_assigned 
--------------------------
                     3667
(1 sor)

=> COMMIT;

Beágyazott tranzakciók

Mentési pontok

Az SQL-ben meghatározott mentési pontok (savepoint), amelyek lehetővé teszik egy tranzakció műveleteinek részleges visszavonását anélkül, hogy a tranzakciót teljesen megszakítanák. De ez nem illeszkedik a fenti sémába, mivel a tranzakciónak egy státusza van az összes módosításához, és fizikailag egyetlen adat sem vonódik vissza.

Ennek a funkciónak a megvalósításához a mentési ponttal rendelkező tranzakciót több különálló beágyazott tranzakcióra (subtransaction) bontják, amelyek státuszával külön-külön lehet gazdálkodni.

A beágyazott tranzakcióknak saját számuk van (nagyobb, mint a fő tranzakció száma). A beágyazott tranzakciók státuszát a megszokott módon rögzítik az XACT-ban, de a végső státusz a fő tranzakció státuszától függ: ha az visszavonásra kerül, akkor az összes beágyazott tranzakciót is visszavonják.

A tranzakciók beágyazottságáról szóló információk a PGDATA/pg_subtrans könyvtárban található fájlokban vannak tárolva. A fájlokhoz való hozzáférés a példány közös memóriájában elhelyezett puffereken keresztül történik, amelyek ugyanolyan módon szerveződnek, mint az XACT pufferei.

Ne keverd össze a beágyazott tranzakciókat és az önálló tranzakciókat. Az önálló tranzakciók nem függenek egymástól, míg a beágyazottak függenek. Az általános PostgreSQL-ben nincsenek önálló tranzakciók, és talán ez így van a legjobban: ezekre a dolgokra nagyon ritkán van szükség, és a jelenlétük más adatbázis-kezelő rendszerekben visszaélésekhez vezet, amelyekből végül mindenki szenved.

Tisztítsuk meg a táblázatot, indítsuk el a tranzakciót, és illesszük 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 érkezik a mentési pont, és beszúrunk egy újabb sort.

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

Megjegyzendő, hogy a txid_current() függvény a fő tranzakció számát adja vissza, nem a beágyazottét.

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

Visszatérünk a mentési pontra, és beszúrjuk 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 látjuk a törölt beágyazott tranzakció által hozzáadott sort.

Rögzítjük 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ó saját állapotot kap.

Meg kell jegyezni, hogy a beágyazott tranzakciók nem használhatók explicit módon az SQL-ben, vagyis nem lehet új tranzakciót indítani anélkül, hogy a meglévőt ne zárnánk le. Ez a mechanizmus implicit módon aktiválódik mentési pontok használatakor, valamint PL/pgSQL kivételek kezelésekor és egyéb egzotikus esetekben is.

=> BEGIN;
Kezd
=> BEGIN;
FIGYELMEZTETÉS: már folyamatban van egy tranzakció
BEGIN
=> COMMIT;
COMMIT
=> COMMIT;
FIGYELMEZTETÉS: nincs folyamatban tranzakció
COMMIT

Hibák és az operációk atomaritása

Mi történik, ha egy művelet végrehajtásakor hiba lép fel? Például:

=> 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 lépett fel. Most a tranzakció megszakadt, és ebben nem engedélyezett semmilyen művelet:

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

És még ha megpróbálnánk is rögzíteni a változtatásokat, a PostgreSQL bejelenti a megszakítást:

=> COMMIT;
ROLLBACK

Miért nem lehet folytatni a tranzakciót hiba után? Az a helyzet, hogy a hiba olyan módon történt, hogy hozzáférést nyerhettünk volna a módosítások egy részéhez - megsérült volna az atomicitás, még a tranzakció, sőt az operátor szintjén is. Mint a példánkban, ahol az operátor a hiba előtt sikeresen frissített egy sort:

=> SELECT * FROM heap_page('t',0);
 ctid  | állapot |   xmin   | xmax  | t_ctid 
-------+--------+----------+-------+--------
 (0,1) | normál | 3669 (c) | 3672  | (0,4)
 (0,2) | normál | 3670 (a) | 0 (a) | (0,2)
 (0,3) | normál | 3671 (c) | 0 (a) | (0,3)
 (0,4) | normál | 3672     | 0 (a) | (0,4)
(4 sor)

Érdemes megjegyezni, hogy a psql rendelkezik egy üzemmóddal, amely lehetővé teszi a tranzakció folytatását a hiba után, mintha az elhibázott operátor intézkedései visszavonásra kerülnének.

=> állítsa be az ON_ERROR_ROLLBACK értékét be
=> KEZDJÜK;
=> VÁLASZTASSON * 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ójában implicit mentési pontot állít be minden parancs elé, és ha hiba lép fel, visszaáll hozzá. Ez a mód nem használható alapértelmezetten, mivel a mentési pontok beállítása (még visszavonás nélkül is) jelentős költségekkel jár.

Folytatás.

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster