MVCC-3. String-versies

We hebben dus rekening gehouden met kwesties die daarmee verband houden isolatie, en trok zich terug het organiseren van gegevens op een laag niveau. En uiteindelijk kwamen we bij het meest interessante deel: de snaarversies.

Titel

Zoals we al hebben gezegd, kan elke rij tegelijkertijd in verschillende versies in de database voorkomen. De ene versie moet op de een of andere manier van de andere worden onderscheiden. Hiertoe heeft elke versie twee markeringen die de “tijd” van actie van deze versie bepalen (xmin en xmax). Tussen aanhalingstekens - omdat niet de tijd als zodanig wordt gebruikt, maar een speciale oplopende teller. En deze teller is het transactienummer.

(Zoals gewoonlijk is de realiteit ingewikkelder: het transactienummer kan niet voortdurend toenemen vanwege de beperkte bitcapaciteit van de teller. Maar we zullen deze details in detail bekijken als we bij het bevriezen komen.)

Wanneer een rij wordt gemaakt, wordt xmin ingesteld op het transactienummer dat de opdracht INSERT heeft uitgegeven, en wordt xmax blanco gelaten.

Wanneer een rij wordt verwijderd, wordt de xmax-waarde van de huidige versie gemarkeerd met het nummer van de transactie die de DELETE heeft uitgevoerd.

Wanneer een rij wordt gewijzigd door een UPDATE-opdracht, worden er feitelijk twee bewerkingen uitgevoerd: DELETE en INSERT. De huidige versie van de rij stelt xmax gelijk aan het nummer van de transactie die de UPDATE heeft uitgevoerd. Er wordt dan een nieuwe versie van dezelfde string gemaakt; de xmin-waarde valt samen met de xmax-waarde van de vorige versie.

De xmin- en xmax-velden zijn opgenomen in de rijversiekop. Naast deze velden bevat de koptekst nog andere, bijvoorbeeld:

  • infomask is een reeks bits die de eigenschappen van deze versie definiëren. Er zijn er behoorlijk veel; We zullen geleidelijk de belangrijkste overwegen.
  • ctid is een link naar de volgende, nieuwere versie van dezelfde regel. Voor de nieuwste, meest actuele versie van een string verwijst de ctid naar deze versie zelf. Het nummer heeft de vorm (x,y), waarbij x het paginanummer is en y het indexnummer in de array.
  • null bitmap - Markeert de kolommen van een bepaalde versie die een nulwaarde (NULL) bevatten. NULL is niet een van de normale gegevenstypewaarden, dus het attribuut moet afzonderlijk worden opgeslagen.

Als gevolg hiervan is de header behoorlijk groot: minimaal 23 bytes voor elke versie van de regel, en meestal meer vanwege de NULL-bitmap. Als de tabel "smal" is (dat wil zeggen weinig kolommen bevat), kan de overhead meer in beslag nemen dan de nuttige informatie.

invoegen

Laten we eens nader bekijken hoe tekenreeksbewerkingen op laag niveau worden uitgevoerd, te beginnen met het invoegen.

Laten we voor experimenten een nieuwe tabel maken met twee kolommen en een index op één ervan:

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

Laten we één rij invoegen na het starten van een transactie.

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

Hier is ons huidige transactienummer:

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

Laten we eens kijken naar de inhoud van de pagina. Met de functie heap_page_items van de pageinspect-extensie kunt u informatie krijgen over pointers en rijversies:

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

Merk op dat het woord heap in PostgreSQL verwijst naar tabellen. Dit is een ander vreemd gebruik van de term: een hoop is bekend data structuur, wat niets met de tafel gemeen heeft. Hier wordt het woord gebruikt in de betekenis van ‘alles wordt bij elkaar gegooid’, in tegenstelling tot geordende indexen.

De functie toont gegevens “zoals ze zijn”, in een formaat dat moeilijk te begrijpen is. Om erachter te komen, laten we slechts een deel van de informatie achter en ontcijferen we deze:

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

Dit is wat we deden:

  • Een nul toegevoegd aan het indexnummer om het er hetzelfde uit te laten zien als t_ctid: (paginanummer, indexnummer).
  • De status van de lp_flags-aanwijzer ontcijferd. Hier is het "normaal" - dit betekent dat de aanwijzer feitelijk verwijst naar de versie van de string. We zullen later naar andere betekenissen kijken.
  • Van alle informatiebits zijn tot nu toe slechts twee paren geïdentificeerd. De bits xmin_composed en xmin_aborted geven aan of transactienummer xmin is vastgelegd (afgebroken). Twee soortgelijke bits verwijzen naar transactienummer xmax.

Wat zien we? Wanneer u een rij invoegt, verschijnt indexnummer 1 op de tabelpagina, verwijzend naar de eerste en enige versie van de rij.

In de stringversie wordt het xmin-veld gevuld met het huidige transactienummer. De transactie is nog steeds actief, dus zowel de xmin_composed als de xmin_aborted bits zijn niet ingesteld.

Het ctid-veld van de rijversie verwijst naar dezelfde rij. Dit betekent dat er geen nieuwere versie bestaat.

Het xmax-veld is gevuld met een dummynummer 0 omdat deze versie van de rij niet is verwijderd en actueel is. Transacties zullen geen aandacht besteden aan dit getal omdat de xmax_aborted bit is ingesteld.

Laten we nog een stap zetten in de richting van het verbeteren van de leesbaarheid door informatiebits aan transactienummers toe te voegen. En laten we een functie maken, omdat we het verzoek meer dan eens nodig hebben:

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

In deze vorm is het veel duidelijker wat er gebeurt in de koptekst van de rijversie:

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

Soortgelijke, maar aanzienlijk minder gedetailleerde informatie kan uit de tabel zelf worden verkregen, met behulp van pseudo-kolommen xmin en xmax:

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

Fixatie

Als een transactie met succes is voltooid, moet u de status ervan onthouden - houd er rekening mee dat deze is vastgelegd. Hiervoor wordt een structuur gebruikt genaamd XACT (en vóór versie 10 heette dit CLOG (commit log) en deze naam is nog steeds op verschillende plaatsen terug te vinden).

XACT is geen systeemcatalogustabel; dit zijn de bestanden in de map PGDATA/pg_xact. Ze hebben twee bits voor elke transactie: vastgelegd en afgebroken - net als in de rijversiekop. Deze informatie is uitsluitend voor het gemak in verschillende bestanden verdeeld; we komen hierop terug als we overwegen om de gegevens te bevriezen. En het werken met deze bestanden wordt pagina voor pagina uitgevoerd, net als bij alle andere.

Wanneer er dus een transactie in XACT wordt vastgelegd, wordt de vastgelegde bit voor deze transactie ingesteld. En dit is alles wat er gebeurt tijdens het committen (hoewel we het nog niet hebben over het pre-recording log).

Wanneer een andere transactie toegang krijgt tot de tabelpagina die we zojuist hebben bekeken, zal deze verschillende vragen moeten beantwoorden.

  1. Is de xmin-transactie voltooid? Als dit niet het geval is, mag de gemaakte versie van de tekenreeks niet zichtbaar zijn.
    Deze controle wordt uitgevoerd door naar een andere structuur te kijken, die zich in het gedeelde geheugen van de instantie bevindt en ProcArray heet. Het bevat een lijst met alle actieve processen en voor elk proces wordt het nummer van de huidige (actieve) transactie aangegeven.
  2. Indien voltooid, hoe dan - door vast te leggen of te annuleren? Bij annulering zou de rijversie ook niet zichtbaar moeten zijn.
    Dit is precies waar XACT voor is. Maar hoewel de laatste pagina's van XACT in buffers in RAM worden opgeslagen, is het nog steeds duur om XACT elke keer te controleren. Daarom wordt, zodra de transactiestatus is bepaald, deze naar de xmin_composed en xmin_aborted bits van de stringversie geschreven. Als een van deze bits is ingesteld, wordt de status van transactie xmin als bekend beschouwd en hoeft de volgende transactie geen toegang te hebben tot XACT.

Waarom worden deze bits niet ingesteld door de transactie zelf die de invoeging uitvoert? Wanneer er een insertie plaatsvindt, weet de transactie nog niet of deze zal slagen. En op het moment van committen is het niet meer duidelijk welke regels op welke pagina’s zijn gewijzigd. Er kunnen veel van dergelijke pagina's zijn, en het onthouden ervan is niet winstgevend. Bovendien kunnen sommige pagina's uit de buffercache naar schijf worden verwijderd; ze opnieuw lezen om de bits te veranderen zou de commit aanzienlijk vertragen.

Het nadeel van de besparingen is dat na wijzigingen elke transactie (zelfs een die een eenvoudige leesbewerking uitvoert - SELECT) gegevenspagina's in de buffercache kan gaan veranderen.

Laten we de wijziging dus oplossen.

=> COMMIT;

Er is niets veranderd op de pagina (maar we weten dat de transactiestatus al is vastgelegd in XACT):

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

Nu zal de transactie die als eerste toegang krijgt tot de pagina de xmin-transactiestatus moeten bepalen en deze naar de informatiebits moeten schrijven:

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

verwijdering

Wanneer een rij wordt verwijderd, wordt het nummer van de huidige verwijdertransactie naar het xmax-veld van de huidige versie geschreven, en wordt de xmax_aborted-bit gewist.

Houd er rekening mee dat de ingestelde waarde van xmax die overeenkomt met de actieve transactie, fungeert als een rijvergrendeling. Als een andere transactie deze rij wil bijwerken of verwijderen, moet deze wachten tot transactie xmax is voltooid. Over blokkeren zullen we later meer praten. Voorlopig merken we alleen op dat het aantal rijsloten onbeperkt is. Ze nemen geen ruimte in RAM in beslag en de systeemprestaties hebben geen last van hun aantal. Het is waar dat ‘lange’ transacties nog andere nadelen hebben, maar daarover later meer.

Laten we de regel verwijderen.

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

We zien dat het transactienummer in het xmax-veld staat, maar de informatiebits zijn niet ingesteld:

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

annulering

Het afbreken van wijzigingen werkt op dezelfde manier als het vastleggen, alleen in XACT wordt het afgebroken bit ingesteld voor de transactie. Ongedaan maken gaat net zo snel als vastleggen. Hoewel het commando ROLLBACK heet, worden de wijzigingen niet teruggedraaid: alles wat de transactie op de gegevenspagina's heeft weten te veranderen, blijft ongewijzigd.

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

Wanneer de pagina wordt geopend, wordt de status gecontroleerd en wordt de xmax_aborted hintbit ingesteld op de rijversie. Het xmax-nummer zelf blijft op de pagina staan, maar niemand zal ernaar kijken.

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

Bijwerken

De update werkt alsof eerst de huidige versie van de rij wordt verwijderd en vervolgens een nieuwe wordt ingevoegd.

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

De query levert één regel op (nieuwe versie):

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

Maar op de pagina zien we beide versies:

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

De verwijderde versie wordt gemarkeerd met het huidige transactienummer in het xmax-veld. Bovendien wordt deze waarde over de oude geschreven, omdat de vorige transactie is geannuleerd. En het xmax_aborted-bit wordt gewist omdat de status van de huidige transactie nog niet bekend is.

De eerste versie van de regel verwijst nu naar de tweede (t_ctid-veld) als de nieuwere.

Er verschijnt een tweede index op de indexpagina en een tweede rij verwijst naar de tweede versie op de tabelpagina.

Net als bij verwijderen is de xmax-waarde in de eerste versie van de rij een indicatie dat de rij is vergrendeld.

Laten we de transactie voltooien.

=> COMMIT;

Index

Tot nu toe hebben we het alleen over tabelpagina's gehad. Wat gebeurt er binnen de indexen?

De informatie op indexpagina's varieert sterk, afhankelijk van het specifieke type index. En zelfs één type index heeft verschillende soorten pagina's. Een B-tree heeft bijvoorbeeld een metadatapagina en ‘gewone’ pagina’s.

De pagina heeft echter meestal een reeks verwijzingen naar de rijen en de rijen zelf (net als een tabelpagina). Daarnaast is er aan het einde van de pagina ruimte voor bijzondere gegevens.

Rijen in indexen kunnen ook heel verschillende structuren hebben, afhankelijk van het type index. Voor een B-boom bevatten de rijen die zijn gerelateerd aan leaf-pagina's bijvoorbeeld de indexeringssleutelwaarde en een verwijzing (ctid) naar de overeenkomstige tabelrij. Over het algemeen kan de index op een heel andere manier worden gestructureerd.

Het belangrijkste punt is dat er geen rijversies zijn in indexen van welk type dan ook. Nou ja, of we kunnen aannemen dat elke regel wordt weergegeven door precies één versie. Met andere woorden: er zijn geen xmin- en xmax-velden in de kop van de indexrij. We kunnen ervan uitgaan dat links uit de index naar alle tabelversies van de rijen leiden. U kunt er dus alleen achter komen welke versie de transactie te zien krijgt door naar de tabel te kijken. (Zoals altijd is dit niet de hele waarheid. In sommige gevallen kan de zichtbaarheidskaart het proces optimaliseren, maar we zullen hier later in meer detail naar kijken.)

Tegelijkertijd vinden we op de indexpagina verwijzingen naar beide versies, zowel de huidige als de oude:

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

Virtuele transacties

In de praktijk maakt PostgreSQL gebruik van optimalisaties waarmee het transactienummers kan ‘opslaan’.

Als een transactie alleen gegevens leest, heeft dit geen effect op de zichtbaarheid van rijversies. Daarom geeft het serviceproces eerst een virtuele xid af aan de transactie. Het nummer bestaat uit een proces-ID en een volgnummer.

Het uitgeven van dit nummer vereist geen synchronisatie tussen alle processen en gaat daardoor zeer snel. We zullen nog een andere reden voor het gebruik van virtuele nummers leren kennen als we het over bevriezing hebben.

Er wordt op geen enkele manier rekening gehouden met virtuele getallen in momentopnamen van gegevens.

Op verschillende tijdstippen kunnen er virtuele transacties in het systeem plaatsvinden met nummers die al in gebruik zijn, en dit is normaal. Maar zo'n getal kan niet in gegevenspagina's worden geschreven, omdat de volgende keer dat de pagina wordt geopend, deze alle betekenis kan verliezen.

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

Als een transactie gegevens begint te veranderen, krijgt deze een echt, uniek transactienummer.

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

=> COMMIT;

Geneste transacties

Spaar punten

Gedefinieerd in SQL punten sparen (savepoint), waarmee u een deel van een transactie kunt annuleren zonder deze volledig te onderbreken. Maar dit past niet in het bovenstaande diagram, omdat de transactie voor al zijn wijzigingen dezelfde status heeft en er fysiek geen gegevens worden teruggedraaid.

Om deze functionaliteit te implementeren, wordt een transactie met een savepoint opgesplitst in verschillende afzonderlijke transacties geneste transacties (subtransactie), waarvan de status afzonderlijk kan worden beheerd.

Geneste transacties hebben een eigen nummer (hoger dan het nummer van de hoofdtransactie). De status van geneste transacties wordt op de gebruikelijke manier in XACT vastgelegd, maar de uiteindelijke status is afhankelijk van de status van de hoofdtransactie: als deze wordt geannuleerd, worden ook alle geneste transacties geannuleerd.

Informatie over het nesten van transacties wordt opgeslagen in bestanden in de map PGDATA/pg_subtrans. Bestanden zijn toegankelijk via buffers in het gedeelde geheugen van de instantie, op dezelfde manier georganiseerd als XACT-buffers.

Verwar geneste transacties niet met autonome transacties. Autonome transacties zijn op geen enkele manier van elkaar afhankelijk, maar geneste transacties wel. Er zijn geen autonome transacties in reguliere PostgreSQL, en misschien wel het beste: ze zijn zeer, zeer zelden nodig, en hun aanwezigheid in andere DBMS's lokt misbruik uit, waar iedereen dan onder lijdt.

Laten we de tabel leegmaken, een transactie starten en de rij invoegen:

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

Laten we nu een opslagpunt plaatsen en een nieuwe regel invoegen.

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

Houd er rekening mee dat de functie txid_current() het hoofdtransactienummer retourneert, en niet het geneste transactienummer.

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

Laten we teruggaan naar het opslagpunt en de derde regel invoegen.

=> 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 de pagina blijven we de rij zien die is toegevoegd door de geannuleerde geneste transactie.

Wij herstellen de wijzigingen.

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

Nu kun je duidelijk zien dat elke geneste transactie zijn eigen status heeft.

Houd er rekening mee dat geneste transacties niet expliciet in SQL kunnen worden gebruikt, dat wil zeggen dat u geen nieuwe transactie kunt starten zonder de huidige te voltooien. Dit mechanisme wordt impliciet geactiveerd bij het gebruik van savepoints, maar ook bij het afhandelen van PL/pgSQL-uitzonderingen en in een aantal andere, meer exotische gevallen.

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

Fouten en atomiciteit van operaties

Wat gebeurt er als er een fout optreedt tijdens het uitvoeren van een bewerking? Bijvoorbeeld zoals dit:

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

Er is een fout opgetreden. Nu wordt de transactie als afgebroken beschouwd en zijn er geen bewerkingen toegestaan:

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

En zelfs als u de wijzigingen probeert door te voeren, rapporteert PostgreSQL een afbreking:

=> COMMIT;
ROLLBACK

Waarom kan een transactie niet doorgaan na een mislukking? Feit is dat er een fout zou kunnen ontstaan ​​op een zodanige manier dat we toegang zouden krijgen tot een deel van de veranderingen - de atomiciteit van niet eens de transactie, maar de operator zou worden geschonden. Zoals in ons voorbeeld, waarbij de operator één regel vóór de fout kon bijwerken:

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

Het moet gezegd worden dat psql een modus heeft waarmee de transactie na een fout nog steeds kan doorgaan alsof de acties van de foutieve operator zijn teruggedraaid.

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

Het is niet moeilijk te raden dat psql in deze modus feitelijk een impliciet opslagpunt vóór elke opdracht plaatst, en in geval van een fout een rollback ernaar initieert. Deze modus wordt niet standaard gebruikt, omdat het instellen van savepoints (zelfs zonder er terug naar terug te keren) aanzienlijke overhead met zich meebrengt.

Voortzetting.

Bron: www.habr.com

Voeg een reactie