Laten we dus de kwesties over bekijken, en een zijstap maken naar . En eindelijk zijn we aangekomen bij het meest interessante—de versies van rijen.
De header
Zoals we al zeiden, kan elke rij gelijktijdig in de database in meerdere versies aanwezig zijn. We moeten een versie van de andere onderscheiden. Hiertoe heeft elke versie twee markeringen die de "tijd" van deze versie aangeven (xmin en xmax). In aanhalingstekens—omdat geen tijd zoals zodanig wordt gebruikt, maar een speciale oplopende teller. En deze teller is het transactie nummer.
(Zoals gewoonlijk is het in werkelijkheid allemaal complexer: het transactie nummer kan niet voortdurend toenemen vanwege de beperkte resolutie van de teller. Maar deze details zullen we in detail bekijken wanneer we bij de bevriezing aankomen.)
Wanneer een rij wordt aangemaakt, wordt de waarde van xmin ingesteld op het transactie nummer dat de INSERT opdracht heeft uitgevoerd, en xmax wordt niet ingevuld.
Wanneer een rij wordt verwijderd, wordt de waarde van xmax van de huidige versie gemarkeerd met het transactie nummer dat de DELETE heeft uitgevoerd.
Wanneer een rij wordt gewijzigd met de UPDATE opdracht, worden feitelijk twee operaties uitgevoerd: DELETE en INSERT. In de huidige versie van de rij wordt xmax ingesteld op het transactie nummer dat de UPDATE heeft uitgevoerd. Vervolgens wordt een nieuwe versie van dezelfde rij aangemaakt; de waarde van xmin komt overeen met de waarde van xmax van de vorige versie.
De velden xmin en xmax maken deel uit van de header van de rij versie. Naast deze velden bevat de header ook andere, bijvoorbeeld:
- infomask—een reeks bits die de eigenschappen van deze versie bepalen. Er zijn er behoorlijk veel; de belangrijkste zullen we geleidelijk bekijken.
- ctid—een verwijzing naar de volgende, nieuwere versie van dezelfde rij. Bij de nieuwste, actuele versie van de rij verwijst ctid naar deze versie zelf. Het nummer heeft de vorm (x,y), waarbij x het paginennummer is en y het volgnummer van de pointer in de array.
- een bitmap van onbepaalde waarden—markeert die kolommen van deze versie die een onbepaalde waarde (NULL) bevatten. NULL is geen van de normale waarden van gegevens typen, daarom moet de indicator apart worden opgeslagen.
Als resultaat is de header vrij groot—minimaal 23 bytes per versie van de rij, en meestal meer vanwege de NULL-bitmap. Als de tabel "smal" is (dat wil zeggen, weinig kolommen bevat), kunnen de overheadskosten groter zijn dan de nuttige informatie.
Invoegen
Laten we dieper ingaan op hoe operaties met strings op laag niveau worden uitgevoerd en beginnen met invoegen.
Voor de experimenten maken we een nieuwe tabel met twee kolommen en een index op een van deze kolommen:
=> CREATE TABLE t(
id serial,
s text
);
=> CREATE INDEX ON t(s);
Laten we één regel invoegen, nadat we de transactie zijn begonnen.
=> BEGIN;
=> INSERT INTO t(s) VALUES ('FOO');
Hier is het nummer van onze huidige transactie:
=> SELECT txid_current();
txid_current
--------------
3664
(1 rij)
Laten we kijken naar de inhoud van de pagina. De functie heap_page_items van de extensie pageinspect biedt informatie over de verwijzingen en versies van regels:
=> 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
Opmerking: in PostgreSQL verwijst het woord heap (hoop) naar tabellen. Dit is weer een vreemd gebruik van de term - een hoop is een bekende , die niets met een tabel te maken heeft. Hier wordt dit woord gebruikt in de zin van 'alles is in een hoop gegooid', in tegenstelling tot geordende indexen.
De functie toont de gegevens 'zoals ze zijn', in een moeilijk te begrijpen formaat. Om het te begrijpen, zullen we alleen een deel van de informatie behouden en deze ontcijferen:
=> 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 hebben gedaan:
- We hebben een nul toegevoegd aan het nummer van de verwijzing om het gelijk te maken aan t_ctid: (pagina nummer, verwijzing nummer).
- We hebben de status van de verwijzing lp_flags ontcijferd. Hier is hij 'normal' - dat betekent dat de verwijzing daadwerkelijk naar de versie van de regel verwijst. Andere waarden zullen we later bekijken.
- Van alle informatiebits hebben we voorlopig alleen twee paren geselecteerd. De bits xmin_committed en xmin_aborted geven aan of de transactie met nummer xmin is bevestigd (geannuleerd). Twee vergelijkbare bits verwijzen naar de transactie met nummer xmax.
Wat zien we? Bij het invoegen van een tuple in een tabelpagina verschijnt een aanwijzer met het nummer 1, verwijzend naar de eerste en enige versie van de tuple.
In de versie van de tuple is het veld xmin ingevuld met het nummer van de huidige transactie. De transactie is nog actief, daarom zijn beide bits xmin_committed en xmin_aborted niet ingesteld.
Het ctid-veld van de versie van de tuple verwijst naar dezelfde tuple. Dit betekent dat er geen nieuwere versie bestaat.
Het veld xmax is ingevuld met de fictieve nummer 0, omdat deze versie van de tuple niet verwijderd is en actueel is. Transacties letten niet op dit nummer omdat de bit xmax_aborted is ingesteld.
Laten we een stap verder gaan om de leesbaarheid te verbeteren door informatieve bits aan de transactienummers toe te voegen. En we creëren een functie, omdat we de query nog meer zullen gebruiken:
=> 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 in de header van de tuple versie gebeurt:
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+------+-------+--------
(0,1) | normal | 3664 | 0 (a) | (0,1)
(1 rij)
Soortgelijke, maar veel minder gedetailleerde informatie kan ook uit de tabel zelf worden verkregen, waarbij gebruik wordt gemaakt van de pseudokolommen xmin en xmax:
=> SELECT xmin, xmax, * FROM t;
xmin | xmax | id | s
------+------+----+-----
3664 | 0 | 1 | FOO
(1 rij)
Vastlegging
Bij een succesvolle afronding van de transactie moet de status worden onthouden - markeren dat deze is vastgelegd. Hiervoor wordt een structuur gebruikt, die XACT wordt genoemd (en vóór versie 10 CLOG werd genoemd, en deze naam kan nog steeds in verschillende contexten worden aangetroffen).
XACT is geen systeemcatalogustabel; dit zijn bestanden in de PGDATA/pg_xact-directory. Voor elke transactie zijn er twee bits gereserveerd: committed en aborted - precies zoals in de header van de tuple versie. Deze informatie is over meerdere bestanden verdeeld enkel voor het gemak, we komen hier later op terug wanneer we de freeze bespreken. De werking met deze bestanden gebeurt pagina voor pagina, net als met alle andere.
Bij het vastleggen van de transactie in XACT wordt de bit committed ingesteld voor deze transactie. En dat is alles wat er gebeurt bij de vastlegging (hoewel we momenteel niet over het pre-write log spreken).
Wanneer een andere transactie de tabelpagina benadert die we zojuist hebben bekeken, moet deze enkele vragen beantwoorden.
- Is de transactie xmin voltooid? Als dat niet het geval is, mag de aangemaakte versie van de rij niet zichtbaar zijn.
Zo'n controle wordt uitgevoerd door een andere structuur te bekijken, die zich in het gedeelde geheugen van de instantie bevindt en ProcArray wordt genoemd. Hierin staat een lijst van alle actieve processen, en voor elk is het nummer van zijn huidige (actieve) transactie aangegeven. - Als de transactie is voltooid, is het dan via vastlegging of annulering? Als het via annulering is, mag de versie van de rij ook niet zichtbaar zijn.
Dit is precies waarvoor XACT nodig is. Echter, hoewel de laatste pagina's van XACT in buffers in het RAM worden opgeslagen, is het elke keer controleren van XACT kostbaar. Daarom wordt de eenmaal vastgestelde status van de transactie vastgelegd in de bits xmin_committed en xmin_aborted van de versie van de rij. Als een van deze bits is ingesteld, wordt de status van de transactie xmin als bekend beschouwd en hoeft de volgende transactie XACT niet meer te raadplegen.
Waarom worden deze bits niet ingesteld door de transactie die de invoeging uitvoert? Bij een invoeging weet de transactie nog niet of deze succesvol zal zijn. En op het moment van vastlegging is het al onduidelijk welke specifieke rijen in welke specifieke pagina's zijn gewijzigd. Er kunnen veel van dergelijke pagina's zijn, en het is ongunstig om ze te onthouden. Bovendien kan een deel van de pagina's uit de buffer cache naar de schijf zijn geweerd; deze opnieuw lezen om de bits te wijzigen zou de vastlegging aanzienlijk vertragen.
De keerzijde van deze besparing is dat na wijzigingen elke transactie (zelfs die een eenvoudige lezing uitvoert — SELECT) kan beginnen met het wijzigen van de gegevenspagina's in de buffer cache.
Laten we de wijziging vastleggen.
=> COMMIT;
Er is niets op de pagina veranderd (maar we weten dat de status van de transactie 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 rij)
Nu moet de transactie die als eerste de pagina benaderde, de status van de transactie xmin bepalen en deze in de informatieve bits vastleggen:
=> SELECT * FROM t;
id | s
----+-----
1 | FOO
(1 rij)
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+----------+-------+--------
(0,1) | normaal | 3664 (c) | 0 (a) | (0,1)
(1 rij)
Verwijderen
Bij het verwijderen van een rij in het xmax-veld van de huidige versie wordt het nummer van de actuele verwijdertransactie opgeslagen en de bit xmax_aborted wordt gereset.
Opmerking dat de ingestelde waarde xmax, die overeenkomt met de actieve transactie, fungeert als een rijvergrendeling. Als een andere transactie deze rij probeert bij te werken of te verwijderen, moet deze wachten tot de transactie xmax is voltooid. We zullen later verder ingaan op vergrendelingen. Voor nu is het belangrijk op te merken dat het aantal rijvergrendelingen onbeperkt is. Ze nemen geen ruimte in het RAM in beslag en de prestaties van het systeem lijden niet onder hun aantal. Echter, lange transacties hebben andere nadelen, maar daar zullen we later op ingaan.
Laten we een rij verwijderen.
=> BEGIN;
=> DELETE FROM t;
=> SELECT txid_current();
txid_current
--------------
3665
(1 rij)
We zien dat het transactienummer is opgeslagen in het xmax-veld, maar de informatiedelen zijn niet ingesteld:
=> SELECT * FROM heap_page('t',0);
ctid | staat | xmin | xmax | t_ctid
-------+--------+----------+------+--------
(0,1) | normaal | 3664 (c) | 3665 | (0,1)
(1 rij)
Annulering
De annulering van wijzigingen werkt op dezelfde manier als het vastleggen, alleen wordt in de XACT voor de transactie de bit aborted ingesteld. Annulering gebeurt net zo snel als vastlegging. Hoewel de opdracht ROLLBACK wordt genoemd, vindt er geen terugdraaiing van wijzigingen plaats: alles wat de transactie heeft kunnen wijzigen in de gegevenspagina's, blijft ongewijzigd.
=> ROLLBACK;
=> SELECT * FROM heap_page('t',0);
ctid | staat | xmin | xmax | t_ctid
-------+--------+----------+------+--------
(0,1) | normaal | 3664 (c) | 3665 | (0,1)
(1 rij)
Bij toegang tot de pagina wordt de status gecontroleerd en in de versie van de rij wordt de bit hint xmax_aborted ingesteld. Het nummer xmax blijft op de pagina staan, maar niemand zal er nu meer naar kijken.
=> SELECT * FROM t;
id | s
----+-----
1 | FOO
(1 rij)
=> SELECT * FROM heap_page('t',0);
ctid | staat | xmin | xmax | t_ctid
-------+--------+----------+----------+--------
(0,1) | normaal | 3664 (c) | 3665 (a) | (0,1)
(1 rij)
Bijwerken
Bijwerken werkt alsof de huidige versie van de rij eerst is verwijderd en vervolgens een nieuwe is ingevoerd.
=> BEGIN;
=> UPDATE t SET s = 'BAR';
=> SELECT txid_current();
txid_current
--------------
3666
(1 rij)
De query retourneert één rij (de nieuwe versie):
=> SELECT * FROM t;
id | s
----+-----
1 | BAR
(1 rij)
Maar op de pagina zien we beide versies:
=> SELECT * FROM heap_page('t',0);
ctid | staat | xmin | xmax | t_ctid
-------+--------+----------+-------+--------
(0,1) | normaal | 3664 (c) | 3666 | (0,2)
(0,2) | normaal | 3666 | 0 (a) | (0,2)
(2 rijen)
De verwijderde versie is gemarkeerd met het nummer van de huidige transactie in het xmax-veld. Dit waarde is op de oude gedocumenteerd, omdat de vorige transactie is geannuleerd. En de bit xmax_aborted is gereset, omdat de status van de huidige transactie nog onbekend is.
De eerste versie van de rij verwijst nu naar de tweede (veld t_ctid) als de nieuwere.
Op de indexpagina verschijnt een tweede aanwijzing en een tweede regel die naar de tweede versie in de tabelpagina verwijst.
Net als bij verwijdering, fungeert de waarde xmax in de eerste versie van de regel als indicatief voor de blokkering van de regel.
Laten we de transactie afsluiten.
=> COMMIT;
Indexen
Tot nu toe hebben we alleen over tabelpagina's gesproken. Maar wat gebeurt er binnen indexen?
Informatie op indexpagina's is sterk afhankelijk van het specifieke type index. En zelfs binnen één type index kunnen er verschillende soorten pagina's zijn. Bijvoorbeeld, een B-boom heeft een pagina met metadata en 'gewone' pagina's.
Desondanks bevat een pagina meestal een array van aanwijzingen naar rijen en de rijen zelf (net als op een tabelpagina). Bovendien is er aan het einde van de pagina ruimte gereserveerd voor speciale gegevens.
De rijen in indexen kunnen ook een zeer verschillende structuur hebben, afhankelijk van het type index. Bijvoorbeeld, voor een B-boom bevatten de rijen die betrekking hebben op bladpagina's de waarde van de indexeringssleutel en een verwijzing (ctid) naar de bijbehorende tabelrij. In het algemeen kan een index heel anders zijn opgebouwd.
Het belangrijkste punt is dat er in indexen van elk type geen versies van rijen zijn. Of men kan stellen dat elke rij precies één versie heeft. Met andere woorden, in de header van een indexrij bevinden zich geen velden xmin en xmax. Men kan ervan uitgaan dat verwijzingen uit de index verwijzen naar alle tabelversies van rijen - zodat het uitzoeken welke versie een transactie zal zien, alleen door naar de tabel te kijken kan worden gedaan. (Zoals gewoonlijk is dit niet de hele waarheid. In sommige gevallen stelt de zichtbaarheidkaart ons in staat om het proces te optimaliseren, maar daar zullen we later dieper op ingaan.)
Op de indexpagina ontdekken we aanwijzingen naar beide versies, zowel de actuele als de oude:
=> SELECT itemoffset, ctid FROM bt_page_items('t_s_idx',1);
itemoffset | ctid
------------+-------
1 | (0,2)
2 | (0,1)
(2 rijen)
Virtuele transacties
In de praktijk gebruikt PostgreSQL een optimalisatie die het mogelijk maakt om transactienummers te 'besparen'.
Als een transactie alleen gegevens leest, heeft deze geen invloed op de zichtbaarheid van versies van rijen. Daarom geeft de bedieningsprocessen in het begin de transactie een virtueel nummer (virtual xid). Dit nummer bestaat uit een proces-ID en een opeenvolgend nummer.
Het geven van dit nummer vereist geen synchronisatie tussen alle processen en gebeurt daarom heel snel. We zullen de andere reden voor het gebruik van virtuele nummers leren kennen wanneer we het over bevriezing hebben.
Virtuele nummers worden niet meegenomen in de datashots.
Op verschillende momenten kunnen er in het systeem virtuele transacties zijn met nummers die al zijn gebruikt, en dat is normaal. Maar zo'n nummer mag niet in de databladen worden opgenomen, omdat het bij de volgende toegang tot de pagina zijn betekenis kan verliezen.
=> BEGIN;
=> SELECT txid_current_if_assigned();
txid_current_if_assigned
--------------------------
(1 rij)
Als de transactie echter gegevens begint te wijzigen, krijgt deze een echt, uniek transactienummer toegewezen.
=> UPDATE accounts SET amount = amount - 1.00;
=> SELECT txid_current_if_assigned();
txid_current_if_assigned
--------------------------
3667
(1 rij)
=> COMMIT;
Geneste transacties
Opslagpunten
In SQL zijn er gedefinieerd opslagpunten (savepoint), die het mogelijk maken om een deel van de transactiebewerkingen ongedaan te maken zonder de hele transactie te onderbreken. Dit past echter niet binnen het bovenstaande schema, omdat de status van de transactie één is voor al haar wijzigingen, terwijl fysiek geen gegevens worden teruggedraaid.
Om deze functionaliteit te implementeren, wordt een transactie met een opslagpunt opgesplitst in verschillende afzonderlijke geneste transacties (subtransaction), waarvan de status afzonderlijk kan worden beheerd.
Geneste transacties hebben hun 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 hangt af van de status van de hoofdtransactie: als deze is geannuleerd, worden ook alle geneste transacties geannuleerd.
Informatie over de geneste transacties wordt opgeslagen in bestanden in de map PGDATA/pg_subtrans. Toegang tot de bestanden gebeurt via buffers in het gedeelde geheugen van het exemplaar, georganiseerd op dezelfde manier als de XACT-buffers.
Verwarren geneste transacties en autonome transacties niet. Autonome transacties zijn volledig onafhankelijk van elkaar, terwijl geneste dat niet zijn. Er zijn geen autonome transacties in gewone PostgreSQL, en dat is misschien maar beter: ze zijn heel, heel zeldzaam nodig, en hun aanwezigheid in andere DBMS leidt tot misbruik, waar iedereen dan later onder lijdt.
Laten we de tabel leegmaken, een transactie starten en een rij invoegen:
=> TRUNCATE TABLE t;
=> BEGIN;
=> INSERT INTO t(s) VALUES ('FOO');
=> SELECT txid_current();
txid_current
--------------
3669
(1 rij)
=> SELECT xmin, xmax, * FROM t;
xmin | xmax | id | s
------+------+----+-----
3669 | 0 | 2 | FOO
(1 rij)
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+------+-------+--------
(0,1) | normaal | 3669 | 0 (a) | (0,1)
(1 rij)
Laten we nu een savepoint instellen en nog een rij invoegen.
=> SAVEPOINT sp;
=> INSERT INTO t(s) VALUES ('XYZ');
=> SELECT txid_current();
txid_current
--------------
3669
(1 rij)
Let op dat de functie txid_current() het nummer van de hoofdtansactie geeft, en niet van de geneste transactie.
=> SELECT xmin, xmax, * FROM t;
xmin | xmax | id | s
------+------+----+-----
3669 | 0 | 2 | FOO
3670 | 0 | 3 | XYZ
(2 rijen)
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+------+-------+--------
(0,1) | normaal | 3669 | 0 (a) | (0,1)
(0,2) | normaal | 3670 | 0 (a) | (0,2)
(2 rijen)
Laten we teruggaan naar het savepoint en een derde rij 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 rijen)
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+----------+-------+--------
(0,1) | normaal | 3669 | 0 (a) | (0,1)
(0,2) | normaal | 3670 (a) | 0 (a) | (0,2)
(0,3) | normaal | 3671 | 0 (a) | (0,3)
(3 rijen)
Op de pagina blijven we de rij zien die is toegevoegd door de geannuleerde geneste transactie.
Laten we de wijzigingen vastleggen.
=> COMMIT;
=> SELECT xmin, xmax, * FROM t;
xmin | xmax | id | s
------+------+----+-----
3669 | 0 | 2 | FOO
3671 | 0 | 4 | BAR
(2 rijen)
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+----------+-------+--------
(0,1) | normaal | 3669 (c) | 0 (a) | (0,1)
(0,2) | normaal | 3670 (a) | 0 (a) | (0,2)
(0,3) | normaal | 3671 (c) | 0 (a) | (0,3)
(3 rijen)
Nu is het duidelijk dat elke geneste transactie zijn eigen status heeft.
Merk op dat geneste transacties niet expliciet kunnen worden gebruikt in SQL, dat wil zeggen dat je geen nieuwe transactie kunt starten zonder de huidige te beëindigen. Dit mechanisme wordt impliciet geactiveerd bij het gebruik van savepoints, maar ook bij het afhandelen van uitzonderingen in PL/pgSQL en in een aantal andere, meer exotische gevallen.
=> BEGIN;
BEGIN
=> BEGIN;
WAARSCHUWING: er is al een transactie bezig
BEGIN
=> COMMIT;
COMMIT
=> COMMIT;
WAARSCHUWING: er is geen transactie bezig
COMMIT
Fouten en atomiciteit van bewerkingen
Wat gebeurt er als er een fout optreedt bij het uitvoeren van een bewerking? Bijvoorbeeld:
=> BEGIN;
=> SELECT * FROM t;
id | s
----+-----
2 | FOO
4 | BAR
(2 rijen)
=> UPDATE t SET s = repeat('X', 1/(id-4));
FOUT: deling door nul
Er is een fout opgetreden. Nu wordt de transactie als onderbroken beschouwd en zijn er geen bewerkingen meer toegestaan:
=> SELECT * FROM t;
FOUT: huidige transactie is afgebroken, opdrachten worden genegeerd tot het einde van het transactieblok
En zelfs als je probeert de wijzigingen vast te leggen, zal PostgreSQL aangeven dat de transactie is geannuleerd:
=> COMMIT;
ROLLBACK
Waarom kan de transactie niet worden voortgezet na een fout? Het probleem is dat de fout zich zo zou hebben voorgedaan dat we toegang zouden hebben gekregen tot een deel van de wijzigingen — de atomiciteit zou niet alleen van de transactie, maar ook van de operator zijn geschonden. Zoals in ons voorbeeld, waar de operator vóór de fout één regel had bijgewerkt:
=> SELECT * FROM heap_page('t',0);
ctid | state | xmin | xmax | t_ctid
-------+--------+----------+-------+--------
(0,1) | normaal | 3669 (c) | 3672 | (0,4)
(0,2) | normaal | 3670 (a) | 0 (a) | (0,2)
(0,3) | normaal | 3671 (c) | 0 (a) | (0,3)
(0,4) | normaal | 3672 | 0 (a) | (0,4)
(4 rijen)
Het moet gezegd worden dat psql een modus heeft waarin het mogelijk is om de transactie na een fout te blijven uitvoeren alsof de acties van de foutieve operator worden teruggedraaid.
=> set ON_ERROR_ROLLBACK on
=> BEGIN;
=> SELECT * FROM t;
id | s
----+-----
2 | FOO
4 | BAR
(2 rijen)
=> UPDATE t SET s = repeat('X', 1/(id-4));
FOUT: deling door nul
=> SELECT * FROM t;
id | s
----+-----
2 | FOO
4 | BAR
(2 rijen)
=> COMMIT;
Het is gemakkelijk te raden dat psql in deze modus feitelijk een impliciete savepoint voor elke opdracht plaatst, en in geval van een fout deze terugdraait naar dat punt. Deze modus is niet standaard ingeschakeld, omdat het instellen van savepoints (zelfs zonder terugdraaien) aanzienlijke overhead met zich meebrengt.
Bron: habr.com
