MVCC-3. String Versiounen

Also hu mir Themen betruecht am Zesummenhang mat Isolatioun, an huet e Réckzuch iwwer organiséieren daten op engem nidderegen Niveau. A schlussendlech si mir zum interessantsten Deel komm - d'Stringversioune.

Header

Wéi mir scho gesot hunn, kann all Zeil gläichzäiteg a verschiddene Versiounen an der Datebank existéieren. Eng Versioun muss iergendwéi vun enger anerer z'ënnerscheeden.Dofir huet all Versioun zwee Marken, déi d'Aktiounszäit vun dëser Versioun bestëmmen (xmin an xmax). An Zitater - well et ass net Zäit als esou, déi benotzt gëtt, mä e spezielle Erhéijung Konter. An dëse Konter ass d'Transaktiounsnummer.

(Wéi gewinnt ass d'Realitéit méi komplizéiert: d'Transaktiounszuel kann net ëmmer eropgoen wéinst der limitéierter Bitkapazitéit vum Comptoir. Mä mir wäerten dës Detailer am Detail kucken, wa mir op d'Gefrier kommen.)

Wann eng Zeil erstallt gëtt, gëtt xmin op d'Transaktiounsnummer gesat, déi den INSERT Kommando erausginn huet, an xmax bleift eidel.

Wann eng Zeil geläscht gëtt, gëtt den xmax Wäert vun der aktueller Versioun mat der Nummer vun der Transaktioun markéiert, déi d'DELETE gemaach huet.

Wann eng Zeil vun engem UPDATE Kommando geännert gëtt, ginn zwou Operatiounen tatsächlech ausgefouert: DELETE an INSERT. Déi aktuell Versioun vun der Zeil setzt xmax gläich wéi d'Zuel vun der Transaktioun déi den UPDATE gemaach huet. Eng nei Versioun vum selwechte String gëtt dann erstallt; säin xmin-Wäert entsprécht dem xmax-Wäert vun der viregter Versioun.

D'xmin an xmax Felder sinn am Zeile Versioun Header abegraff. Zousätzlech zu dëse Felder enthält den Header aner, zum Beispill:

  • infomask ass eng Serie vu Bits déi d'Eegeschafte vun dëser Versioun definéieren. Et sinn zimlech vill vun hinnen; Mir wäerte lues a lues d'Haaptvirdeeler berücksichtegen.
  • ctid ass e Link op déi nächst, méi nei Versioun vun der selwechter Linn. Fir déi neisten, aktuellst Versioun vun engem String, bezitt d'ctid op dës Versioun selwer. D'Zuel huet d'Form (x,y), wou x d'Säitnummer ass, y d'Indexnummer an der Array ass.
  • null Bitmap - Markéiert dës Kolonnen vun enger bestëmmter Versioun déi en Nullwäert (NULL) enthalen. NULL ass net ee vun den normalen Datentyp Wäerter, sou datt den Attribut getrennt gespäichert muss ginn.

Als Resultat ass den Header zimlech grouss - op d'mannst 23 Bytes fir all Versioun vun der Linn, an normalerweis méi wéinst der NULL Bitmap. Wann den Dësch "schmuel" ass (dat heescht, enthält e puer Kolonnen), kann den Overhead méi wéi déi nëtzlech Informatioun ophuelen.

opginn

Loosst eis e méi no kucken wéi niddereg-Level String Operatioune gemaach ginn, ugefaange mat der Insertioun.

Fir Experimenter, loosst eis eng nei Tabell mat zwou Kolonnen an engem Index op eng vun hinnen erstellen:

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

Loosst eis eng Zeil setzen nodeems Dir eng Transaktioun starten.

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

Hei ass eis aktuell Transaktiounsnummer:

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

Loosst eis den Inhalt vun der Säit kucken. D'heap_page_items Funktioun vun der Pageinspect Extensioun erlaabt Iech Informatiounen iwwer Zeigefanger a Zeilversioune ze kréien:

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

Notéiert datt d'Wuert Koup am PostgreSQL op Dëscher bezitt. Dëst ass eng aner komesch Notzung vum Begrëff - e Koup ass bekannt daten Struktur, déi näischt mam Dësch gemeinsam huet. Hei gëtt d'Wuert am Sënn vun "alles zesummegeheien" benotzt, am Géigesaz zu bestallten Indexen.

D'Funktioun weist Daten "wéi et ass", an engem Format dat schwéier ze verstoen ass. Fir et erauszefannen, wäerte mir nëmmen en Deel vun der Informatioun verloossen an se entschlësselen:

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

Hei ass wat mir gemaach hunn:

  • Eng Null op d'Indexnummer bäigefüügt fir datt et d'selwecht ausgesäit wéi t_ctid: (Säitnummer, Indexnummer).
  • Entschlësselt den Zoustand vum lp_flags Pointer. Hei ass et "normal" - dat heescht datt de Pointer tatsächlech op d'Versioun vum String bezitt. Mir wäerte spéider aner Bedeitunge kucken.
  • Vun allen Informatiounsbits sinn bis elo nëmmen zwee Puer identifizéiert ginn. D'xmin_committed an xmin_aborted Bits weisen ob d'Transaktiounsnummer xmin engagéiert ass (ofgebrach). Zwee ähnlech Bits bezéien sech op d'Transaktiounsnummer xmax.

Wat gesi mir? Wann Dir eng Zeil asetzt, erschéngt en Indexnummer 1 op der Tabellesäit, déi op déi éischt an eenzeg Versioun vun der Zeil weist.

An der String Versioun gëtt d'xmin Feld mat der aktueller Transaktiounsnummer gefëllt. D'Transaktioun ass nach ëmmer aktiv, sou datt souwuel d'xmin_committed wéi och xmin_aborted Bits net gesat ginn.

D'Rei Versioun ctid Feld bezitt sech op déi selwecht Zeil. Dëst bedeit datt eng méi nei Versioun net existéiert.

Den xmax Feld ass mat enger Dummy Nummer 0 gefëllt well dës Versioun vun der Zeil net geläscht gouf an aktuell ass. Transaktioune wäerten net op dës Zuel oppassen well den xmax_aborted Bit agestallt ass.

Loosst eis e Schrëtt méi maache fir d'Liesbarkeet ze verbesseren andeems Dir Informatiounsbits un Transaktiounsnummeren bäidréit. A loosst eis eng Funktioun erstellen, well mir d'Ufro méi wéi eemol brauchen:

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

An dëser Form ass et vill méi kloer wat am Header vun der Zeilversioun lass ass:

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

Ähnlech, awer wesentlech manner detailléiert, Informatioun kann aus der Tabell selwer kritt ginn, mat Pseudo-Kolonnen xmin an xmax:

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

Fixatioun

Wann eng Transaktioun erfollegräich ofgeschloss ass, musst Dir säi Status erënneren - notéiert datt et engagéiert ass. Fir dëst ze maachen, gëtt eng Struktur mam Numm XACT benotzt (a virun der Versioun 10 gouf et CLOG genannt (commit log) an dësen Numm kann nach op verschiddene Plazen fonnt ginn.

XACT ass net e System Katalog Dësch; dëst sinn d'Dateien am PGDATA/pg_xact Verzeichnis. Si hunn zwee Bits fir all Transaktioun: engagéiert an ofgebrach - sou wéi an der Rei Versioun Header. Dës Informatioun ass eleng fir d'Bequemlechkeet an e puer Dateien opgedeelt; mir kommen op dëst Thema zréck wa mir d'Gefrierung betruechten. An d'Aarbecht mat dësen Dateien gëtt Säit fir Säit duerchgefouert, wéi mat all deenen aneren.

Also, wann eng Transaktioun am XACT engagéiert ass, gëtt de engagéierte Bit fir dës Transaktioun gesat. An dat ass alles wat während der Verpflichtung geschitt (obwuel mir nach net iwwer de Pre-Recording Log schwätzen).

Wann eng aner Transaktioun op d'Tabellsäit kënnt, déi mir just gekuckt hunn, muss se e puer Froen beäntweren.

  1. Huet d'xmin Transaktioun ofgeschloss? Wann net, da sollt déi erstallt Versioun vum String net sichtbar sinn.
    Dëse Scheck gëtt gemaach andeems Dir eng aner Struktur kuckt, déi an der gemeinsamer Erënnerung vun der Instanz läit a ProcArray genannt gëtt. Et enthält eng Lëscht vun all aktive Prozesser, a fir all eenzel gëtt d'Zuel vu senger aktueller (aktiver) Transaktioun uginn.
  2. Wann ofgeschloss, wéi dann - andeems Dir engagéiert oder annuléiert? Wann annuléiert, da soll d'Rei Versioun och net siichtbar sinn.
    Dëst ass genau wat XACT fir ass. Awer och wann déi lescht Säite vum XACT a Puffer am RAM gespäichert sinn, ass et ëmmer nach deier fir XACT all Kéier ze kontrolléieren. Dofir, wann den Transaktiounsstatus bestëmmt ass, gëtt et op d'xmin_committed an xmin_aborted Bits vun der String Versioun geschriwwe. Wann ee vun dëse Bits agestallt ass, da gëtt den Zoustand vun der Transaktioun xmin als bekannt ugesinn an déi nächst Transaktioun muss net Zougang zu XACT kréien.

Firwat ginn dës Bits net vun der Transaktioun selwer gesat, déi den Insert maachen? Wann en Insert geschitt, weess d'Transaktioun nach net ob et geléngt. An am Moment vun der Verpflichtung ass net méi kloer wéi eng Zeilen a wéi enger Säite geännert goufen. Et kënne vill vun esou Säiten ginn, an hinnen memoriséieren ass net rentabel. Zousätzlech kënnen e puer Säiten aus dem Puffer-Cache op Disk evictéiert ginn; se nach eng Kéier liesen fir d'Bits z'änneren, géif d'Verpflichtung wesentlech verlangsamen.

Den Nodeel vun de Spueren ass datt no Ännerungen all Transaktioun (och een deen eng einfach Liesung mécht - SELECT) kann ufänken Datensäiten am Puffer-Cache z'änneren.

Also, loosst eis d'Ännerung fixéieren.

=> COMMIT;

Näischt huet op der Säit geännert (awer mir wëssen datt den Transaktiounsstatus schonn an XACT opgeholl ass):

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

Elo muss d'Transaktioun déi als éischt op d'Säit zougräift den xmin Transaktiounsstatus bestëmmen an an d'Informatiounsbits schreiwen:

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

Läschen

Wann eng Zeil geläscht gëtt, gëtt d'Zuel vun der aktueller Läschtransaktioun an d'xmax Feld vun der aktueller Versioun geschriwwe, an den xmax_aborted Bit gëtt geläscht.

Bedenkt datt de Set Wäert vun xmax entspriechend der aktiv Transaktioun Akten als Rei Spär. Wann eng aner Transaktioun dës Zeil aktualiséieren oder läschen wëllt, wäert se gezwongen sinn ze waarden bis d'Transaktioun xmax fäerdeg ass. Mir schwätze méi iwwer d'Blockéierung méi spéit. Fir de Moment bemierken mir just datt d'Zuel vun de Reihenschloss onlimitéiert ass. Si huelen net Plaz am RAM an d'Systemleistung leiden net vun hirer Zuel. True, "laang" Transaktiounen hunn aner Nodeeler, awer méi iwwer dat méi spéit.

Loosst eis d'Linn läschen.

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

Mir gesinn datt d'Transaktiounsnummer am xmax Feld geschriwwe gëtt, awer d'Informatiounsbits sinn net gesat:

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

Kënnegung

Ofbriechen Ännerunge funktionnéiert ähnlech wéi eng Verpflichtung, nëmmen am XACT gëtt den ofgebrach Bit fir d'Transaktioun gesat. Undoen ass sou séier wéi engagéieren. Och wann de Kommando ROLLBACK genannt gëtt, ginn d'Ännerungen net zréckgezunn: alles wat d'Transaktioun op den Datensäiten geännert huet bleift onverännert.

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

Wann d'Säit zougänglech ass, gëtt de Status iwwerpréift an den xmax_aborted Hiweisbit gëtt op d'Reiversioun gesat. D'Xmax Nummer selwer bleift op der Säit, awer keen wäert se kucken.

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

Update

D'Aktualiséierung funktionnéiert wéi wann et fir d'éischt déi aktuell Versioun vun der Zeil geläscht huet an duerno eng nei agebaut huet.

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

D'Ufro produzéiert eng Zeil (nei Versioun):

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

Awer op der Säit gesi mir béid Versiounen:

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

Déi geläscht Versioun gëtt mat der aktueller Transaktiounsnummer am xmax Feld markéiert. Ausserdeem gëtt dëse Wäert iwwer déi al geschriwwe, well déi viregt Transaktioun annuléiert gouf. An den xmax_aborted Bit gëtt geläscht well de Status vun der aktueller Transaktioun nach net bekannt ass.

Déi éischt Versioun vun der Linn bezitt sech elo op déi zweet (t_ctid Feld) als déi méi nei.

En zweeten Index erschéngt op der Indexsäit an eng zweet Zeil bezitt sech op déi zweet Versioun op der Tabellesäit.

Just wéi beim Läschen ass den xmax-Wäert an der éischter Versioun vun der Zeil eng Indikatioun datt d'Zeil gespaart ass.

Gutt, loosst eis d'Transaktioun fäerdeg maachen.

=> COMMIT;

Indexen

Bis elo hu mir nëmmen iwwer Tabellesäiten geschwat. Wat geschitt an den Indexen?

D'Informatioun op Indexsäiten variéiert staark ofhängeg vun der spezifescher Aart vum Index. A souguer eng Zort Index huet verschidden Aarte vu Säiten. Zum Beispill, e B-Bam huet eng Metadatensäit an "normale" Säiten.

Wéi och ëmmer, d'Säit huet normalerweis eng Rei vun Hiweiser op d'Reihen an d'Reihen selwer (just wéi eng Tabellesäit). Ausserdeem gëtt et um Enn vun der Säit Plaz fir speziell Donnéeën.

Reihen an Indexen kënnen och ganz ënnerschiddlech Strukturen hunn ofhängeg vun der Aart vum Index. Zum Beispill, fir e B-Bam, enthalen d'Reihen am Zesummenhang mat Blat Säiten den Indexéierungsschlësselwäert an eng Referenz (ctid) op déi entspriechend Tabellerei. Allgemeng kann den Index op eng ganz aner Manéier strukturéiert ginn.

De wichtegste Punkt ass datt et keng Rei Versiounen an Indizes vun all Typ sinn. Ee, oder mir kënnen dovun ausgoen, datt all Linn duerch genee eng Versioun vertruede ass. An anere Wierder, et gi keng xmin an xmax Felder am Index Zeil Header. Mir kënnen dovun ausgoen, datt Linken aus dem Index zu all Dësch Versiounen vun de Zeile féieren - sou kënnt Dir erausfanne wéi eng Versioun d'Transaktioun nëmmen op den Dësch kuckt. (Wéi ëmmer ass dat net déi ganz Wourecht. An e puer Fäll kann d'Visibilitéitskaart de Prozess optimiséieren, awer mir wäerten dat spéider méi am Detail kucken.)

Zur selwechter Zäit fanne mir op der Indexsäit Hiweiser op béid Versiounen, souwuel déi aktuell wéi déi al:

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

Virtuell Transaktiounen

An der Praxis benotzt PostgreSQL Optimisatiounen déi et erlaben Transaktiounsnummeren ze "späicheren".

Wann eng Transaktioun nëmmen Daten liest, huet et keen Effekt op d'Visibilitéit vu Reiversioune. Dofir gëtt de Serviceprozess fir d'éischt e virtuelle Xid op d'Transaktioun eraus. D'Zuel besteet aus enger Prozess-ID an enger Sequenznummer.

Dës Zuel erausginn erfuerdert keng Synchroniséierung tëscht alle Prozesser an ass dofir ganz séier. Mir wäerte mat engem anere Grond kennen fir virtuell Zuelen ze benotzen wa mir iwwer Afréiere schwätzen.

Virtuell Zuelen ginn op keng Aart a Weis an Datesnapshots berücksichtegt.

Op verschiddene Zäitpunkte kënnen et virtuell Transaktiounen am System mat Zuelen sinn, déi scho benotzt goufen, an dat ass normal. Awer esou eng Zuel kann net an Datesäiten geschriwwe ginn, well déi nächst Kéier op d'Säit zougänglech ass, kann se all Bedeitung verléieren.

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

Wann eng Transaktioun ufänkt Daten z'änneren, gëtt et eng richteg, eenzegaarteg Transaktiounsnummer.

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

=> COMMIT;

Nested Transaktiounen

Spuert Punkten

Definéiert an SQL späicheren Punkten (Savepoint), wat Iech erlaabt en Deel vun enger Transaktioun ze annuléieren ouni se komplett ze ënnerbriechen. Awer dëst passt net an den uewe genannten Diagramm, well d'Transaktioun dee selwechte Status fir all seng Ännerungen huet, a kierperlech keng Daten ginn zréckgezunn.

Fir dës Funktionalitéit ëmzesetzen, gëtt eng Transaktioun mat engem Savepoint an e puer separat opgedeelt verstoppt Transaktiounen (Ënnertransaktioun), de Status vun deem kann getrennt geréiert ginn.

Nested Transaktiounen hunn hir eege Zuel (méi héich wéi d'Zuel vun der Haaptrei Transaktioun). De Status vun nestéierten Transaktiounen gëtt op déi üblech Manéier an XACT opgeholl, awer de Finale Status hänkt vum Status vun der Haapttransaktioun of: wann et annuléiert gëtt, da ginn all nestéiert Transaktiounen och annuléiert.

Informatioun iwwer Transaktiounsnesting gëtt a Dateien am PGDATA/pg_subtrans Verzeichnis gespäichert. D'Dateie ginn zougänglech duerch Pufferen an der gemeinsamer Erënnerung vun der Instanz, organiséiert op déiselwecht Manéier wéi XACT Puffer.

Verwiesselt net nested Transaktiounen mat autonomen Transaktiounen. Autonom Transaktiounen hänken op iergendeng Manéier net vuneneen of, awer nested Transaktiounen. Et gi keng autonom Transaktiounen am reguläre PostgreSQL, an, vläicht, zum Beschten: si gi ganz, ganz selten gebraucht, an hir Präsenz an aneren DBMSen provozéiert Mëssbrauch, vun deem jidderee leid.

Loosst eis den Dësch läschen, eng Transaktioun starten an d'Zeil setzen:

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

Loosst eis elo e Spuerpunkt setzen an eng aner Linn setzen.

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

Bedenkt datt d'Funktioun txid_current () d'Haapttransaktiounsnummer zréckkënnt, net déi genetesch Transaktiounsnummer.

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

Loosst eis zréck op de Spuerpunkt rullen an déi drëtt Linn setzen.

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

Op der Säit gesitt mir weider d'Zeil, déi vun der annuléierter nested Transaktioun bäigefüügt gëtt.

Mir fixéieren d'Ännerungen.

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

Elo kënnt Dir kloer gesinn datt all nested Transaktioun säin eegene Status huet.

Bedenkt datt nested Transaktiounen net explizit an SQL benotzt kënne ginn, dat heescht, Dir kënnt net eng nei Transaktioun starten ouni déi aktuell ofzeschléissen. Dëse Mechanismus gëtt implizit aktivéiert wann Dir Savepoints benotzt, wéi och wann Dir PL / pgSQL Ausnahmen behandelt an an enger Rei vun aneren, méi exotesch Fäll.

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

Feeler an Atomitéit vun Operatiounen

Wat geschitt wann e Feeler geschitt beim Ausféieren vun enger Operatioun? Zum Beispill, wéi dëst:

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

E Feeler ass geschitt. Elo gëtt d'Transaktioun als ofgebrach ugesinn a keng Operatiounen sinn erlaabt:

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

An och wann Dir probéiert d'Ännerungen ze engagéieren, wäert PostgreSQL en Ofbriechen mellen:

=> COMMIT;
ROLLBACK

Firwat kann eng Transaktioun no engem Echec net weidergoen? D'Tatsaach ass, datt e Feeler an esou enger Aart a Weis entstoen kéint, datt mir Zougang zu engem Deel vun de Ännerunge kréien géif - d'Atomitéit vun net souguer d'Transaktioun, mä de Bedreiwer géif verletzen. Wéi an eisem Beispill, wou de Bedreiwer et fäerdeg bruecht huet eng Zeil virum Feeler ze aktualiséieren:

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

Et muss gesot ginn datt psql e Modus huet, deen d'Transaktioun nach ëmmer erlaabt no engem Versoen weiderzemaachen, wéi wann d'Aktiounen vum falschen Bedreiwer zréckgerullt goufen.

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

Et ass net schwéier ze roden datt an dësem Modus psql tatsächlech en impliziten Spuerpunkt virun all Kommando setzt, an am Fall vun engem Ausfall eng Rollback initiéiert. Dëse Modus gëtt net als Standard benotzt, well d'Späicherpunkte setzen (och ouni se zréck ze rullen) bedeitend Overhead implizéiert.

Fir weider ze goen.

Source: will.com

Setzt e Commentaire