MVCC-3. Zeilenversionen

Wir haben uns also mit Fragen im Zusammenhang mit der Isolation befasst und einen Exkurs über die Organisation von Daten auf niedriger Ebene gemacht . Und schließlich zum interessantesten - zur Version der Zeilen.

Überschrift


Wie bereits erwähnt, kann jede Zeile in mehreren Versionen gleichzeitig in der Datenbank vorhanden sein. Eine Version muss irgendwie von der anderen unterschieden werden. Zu diesem Zweck hat jede Version zwei Markierungen, die die "Zeit" der Aktion dieser Version bestimmen (xmin und xmax). In Anführungszeichen - weil nicht die Zeit als solche verwendet wird, sondern ein spezieller Inkrementierungszähler. Und dieser Zähler ist die Transaktionsnummer.

(Wie üblich ist es tatsächlich komplizierter: Die Transaktionsnummer kann aufgrund der begrenzten Bitkapazität des Zählers nicht ständig erhöht werden. Wir werden diese Details jedoch im Detail betrachten, wenn wir zum Einfrieren kommen.)

Beim Erstellen der Zeile wird xmin auf die Nummer der Transaktion festgelegt, die den Befehl INSERT ausgeführt hat, und xmax wird nicht ausgefüllt.

Wenn eine Zeile gelöscht wird, wird der xmax-Wert der aktuellen Version mit der Transaktionsnummer markiert, die DELETE ausgeführt hat.

Wenn eine Zeile mit dem Befehl UPDATE geändert wird, werden tatsächlich zwei Operationen ausgeführt: DELETE und INSERT. In der aktuellen Version der Zeile wird xmax gleich der Nummer der Transaktion gesetzt, die UPDATE ausgeführt hat. Dann wird eine neue Version derselben Zeile erstellt. Der xmin-Wert entspricht dem xmax-Wert der vorherigen Version.

Die Felder xmin und xmax sind im Zeilenversionsheader enthalten. Zusätzlich zu diesen Feldern enthält der Header weitere, zum Beispiel:

  • Infomask - Eine Reihe von Bits, die die Eigenschaften dieser Version definieren. Es gibt ziemlich viele von ihnen; Die wichtigsten werden wir nach und nach betrachten.
  • ctid - ein Link zur nächsten, neueren Version derselben Zeile. In der neuesten, aktuellsten Version der Zeichenfolge bezieht sich ctid auf diese Version selbst. Die Nummer hat die Form (x, y), wobei x die Seitenzahl und y die Seriennummer des Zeigers im Array ist.
  • Bitmap undefinierter Werte - Markiert die Spalten dieser Version, die einen undefinierten Wert (NULL) enthalten. NULL ist nicht einer der üblichen Werte von Datentypen, daher muss das Attribut separat gespeichert werden.

Infolgedessen ist der Header ziemlich groß - mindestens 23 Bytes pro Version der Zeichenfolge und normalerweise mehr aufgrund der NULL-Bitmap. Wenn die Tabelle "eng" ist (dh nur wenige Spalten enthält), kann der Overhead mehr als nützliche Informationen erfordern.

Einfügen


Schauen wir uns genauer an, wie Zeichenfolgenoperationen auf niedriger Ebene ausgeführt werden, und beginnen wir mit dem Einfügen.

Erstellen Sie für Experimente eine neue Tabelle mit zwei Spalten und einem Index für eine davon:

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

Fügen Sie nach dem Start der Transaktion eine Zeile ein.

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

Hier ist die Nummer unserer aktuellen Transaktion:

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

Schauen Sie sich den Inhalt der Seite an Die Funktion heap_page_items der Erweiterung pageinspect bietet Informationen zu Zeigern und Zeilenversionen:

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

Beachten Sie, dass sich das Wort Heap (Heap) in PostgreSQL auf Tabellen bezieht. Dies ist eine weitere seltsame Verwendung des Begriffs - Heap ist eine bekannte Datenstruktur , die nichts mit einer Tabelle zu tun hat. Hier wird dieses Wort im Gegensatz zu geordneten Indizes im Sinne von "alles ist auf einem Haufen gestapelt" verwendet.

Die Funktion zeigt Daten "wie sie sind" in einem schwer lesbaren Format an. Um dies zu verstehen, lassen wir nur einen Teil der Informationen und entschlüsseln sie:

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

Folgendes haben wir getan:

  • Wir haben der Indexnummer eine Null hinzugefügt, um sie in die gleiche Form wie t_ctid zu bringen: (Seitenzahl, Indexnummer).
  • Entschlüsselt den Status des Zeigers lp_flags. Hier ist es "normal" - das bedeutet, dass der Zeiger wirklich auf die Version der Zeichenfolge verweist. Andere Werte werden später berücksichtigt.
  • Von allen Informationsbits wurden bisher nur zwei Paare zugeordnet. Die Bits xmin_committed und xmin_aborted geben an, ob die Transaktion mit der xmin-Nummer festgeschrieben (abgebrochen) wird. Zwei ähnliche Bits beziehen sich auf die Transaktionsnummer xmax.

Was sehen wir? Wenn Sie eine Zeile in die Tabellenseite einfügen, wird ein Zeiger mit der Nummer 1 angezeigt, der auf die erste und einzige Version der Zeile verweist.

In der Version der Zeile wird das Feld xmin mit der Nummer der aktuellen Transaktion gefüllt. Die Transaktion ist noch aktiv, sodass beide Bits xmin_committed und xmin_aborted nicht gesetzt sind.

Das Feld ctid der Zeilenversion bezieht sich auf dieselbe Zeile. Dies bedeutet, dass keine neuere Version vorhanden ist.

Das xmax-Feld wird mit einer Dummy-Nummer 0 gefüllt, da diese Version der Zeile nicht gelöscht wird und relevant ist. Transaktionen berücksichtigen diese Nummer nicht, da das Bit xmax_aborted gesetzt ist.

Machen wir noch einen Schritt, um die Lesbarkeit zu verbessern, indem wir den Transaktionsnummern Informationsbits hinzufügen. Und wir werden eine Funktion erstellen, da wir die Anfrage mehr als einmal benötigen werden:

 => 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 dieser Form ist viel klarer, was in der Kopfzeile der Version der Zeichenfolge passiert:

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

Ähnliche, aber wesentlich weniger detaillierte Informationen können aus der Tabelle selbst unter Verwendung der Pseudospalten xmin und xmax erhalten werden:

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

Fixierung


Nach erfolgreichem Abschluss der Transaktion müssen Sie sich ihren Status merken - beachten Sie, dass sie behoben ist. Verwenden Sie dazu eine Struktur namens XACT (und vor Version 10 hieß sie CLOG (Commit Log) und dieser Name befindet sich immer noch an verschiedenen Stellen).

XACT ist keine Systemkatalogtabelle. Dies sind die Dateien im Verzeichnis PGDATA / pg_xact. In ihnen werden für jede Transaktion zwei Bits zugewiesen: festgeschrieben und abgebrochen - genau wie im Header der Version der Zeile. Diese Informationen sind nur der Einfachheit halber in mehrere Dateien unterteilt. Wir werden auf dieses Problem zurückkommen, wenn wir das Einfrieren in Betracht ziehen. Und die Arbeit mit diesen Dateien wird wie bei allen anderen Seite für Seite ausgeführt.

Wenn Sie also eine Transaktion in XACT festschreiben, wird das festgeschriebene Bit für diese Transaktion gesetzt. Und das ist alles, was während des Commits passiert (obwohl wir noch nicht über das Voraufzeichnungsjournal sprechen).

Wenn eine andere Transaktion auf die gerade betrachtete Tabellenseite zugreift, muss sie einige Fragen beantworten.

  1. Wurde die Transaktion xmin abgeschlossen? Wenn nicht, sollte die generierte Version der Zeichenfolge nicht sichtbar sein.
    Eine solche Überprüfung wird durchgeführt, indem eine weitere Struktur betrachtet wird, die sich im gemeinsam genutzten Speicher der Instanz befindet und als ProcArray bezeichnet wird. Es enthält eine Liste aller aktiven Prozesse und für jeden wird die Nummer seiner aktuellen (aktiven) Transaktion angegeben.
  2. Wenn abgeschlossen, wie - durch Fixierung oder Stornierung? Wenn abgebrochen, sollte auch die Version der Zeichenfolge nicht sichtbar sein.
    Genau dafür ist XACT gedacht. Obwohl die letzten XACT-Seiten in Puffern im RAM gespeichert sind, muss der XACT nicht jedes Mal überprüft werden. Daher wird der Status einer einmal geklärten Transaktion in den Bits xmin_committed und xmin_aborted der Zeilenversion aufgezeichnet. Wenn eines dieser Bits gesetzt ist, wird der Status der Transaktion xmin als bekannt angesehen und die nächste Transaktion muss nicht mehr auf XACT zugreifen.

Warum werden diese Bits nicht von der Transaktion selbst gesetzt, die das Einfügen durchführt? Wenn eine Einfügung erfolgt, weiß die Transaktion noch nicht, ob sie erfolgreich abgeschlossen wird. Und zum Zeitpunkt der Korrektur ist bereits nicht klar, welche Zeilen auf welchen Seiten geändert wurden. Es kann viele solcher Seiten geben, und das Auswendiglernen ist nachteilig. Außerdem kann ein Teil der Seiten aus dem Puffercache auf die Festplatte verschoben werden. Ein erneutes Lesen, um die Bits zu ändern, würde bedeuten, dass das Festschreiben erheblich verlangsamt wird.

Der Nachteil der Einsparungen besteht darin, dass nach den Änderungen bei jeder Transaktion (auch beim einfachen Lesen - SELECT) die Datenseiten im Puffercache geändert werden können.

Korrigieren Sie also die Änderung.

 => COMMIT; 

Auf der Seite hat sich nichts geändert (aber wir wissen, dass der Transaktionsstatus bereits in XACT aufgezeichnet wurde):

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

Jetzt muss die Transaktion, die zuerst auf die Seite zugreift, den Transaktionsstatus xmin ermitteln und in die Informationsbits schreiben:

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


Wenn eine Zeile gelöscht wird, wird die Nummer der aktuellen Löschtransaktion im Feld xmax der aktuellen Version aufgezeichnet und das Bit xmax_aborted zurückgesetzt.

Beachten Sie, dass der festgelegte xmax-Wert, der der aktiven Transaktion entspricht, als Zeilensperre fungiert. Wenn eine andere Transaktion diese Zeile aktualisieren oder löschen soll, muss sie warten, bis die xmax-Transaktion abgeschlossen ist. Wir werden später mehr über Schlösser sprechen. Im Moment stellen wir nur fest, dass die Anzahl der Zeilensperren unbegrenzt ist. Sie nehmen keinen Platz im RAM ein und die Systemleistung leidet nicht unter ihrer Menge. Zwar haben „lange“ Transaktionen andere Nachteile, aber dazu später mehr.

Löschen Sie die Zeile.

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

Wir sehen, dass die Transaktionsnummer im xmax-Feld aufgezeichnet ist, aber die Informationsbits nicht gesetzt sind:

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

Abbrechen


Das Zurücksetzen von Änderungen funktioniert ähnlich wie das Festschreiben, nur in XACT wird für die Transaktion das abgebrochene Bit gesetzt. Die Stornierung erfolgt so schnell wie das Festschreiben. Obwohl der Befehl ROLLBACK heißt, wird die Änderung nicht zurückgesetzt: Alles, was die Transaktion auf den Datenseiten ändern konnte, bleibt unverändert.

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

Beim Zugriff auf die Seite wird der Status überprüft und das Hinweisbit xmax_aborted in der Version der Zeile gesetzt. Die xmax-Nummer selbst bleibt auf der Seite, aber niemand wird sie ansehen.

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


Das Update funktioniert so, als würde zuerst die aktuelle Version der Zeile gelöscht und dann eine neue eingefügt.

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

Die Anfrage erzeugt eine Zeile (neue Version):

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

Aber auf der Seite sehen wir beide Versionen:

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

Die Remote-Version ist mit der aktuellen Transaktionsnummer im Feld xmax gekennzeichnet. Außerdem wird dieser Wert über den alten geschrieben, da die vorherige Transaktion abgebrochen wurde. Das Bit xmax_aborted wird zurückgesetzt, da der Status der aktuellen Transaktion noch unbekannt ist.

Die erste Version der Zeile bezieht sich jetzt auf die zweite (t_ctid-Feld) als neuere.

Auf der Indexseite werden ein zweiter Zeiger und eine zweite Zeile angezeigt, die auf die zweite Version der Tabellenseite verweisen.

Wie beim Löschen ist der xmax-Wert in der ersten Version der Zeichenfolge ein Zeichen dafür, dass die Zeichenfolge gesperrt ist.

Nun, schließen Sie die Transaktion ab.

 => COMMIT; 

Indizes


Bisher haben wir nur über Tabellenseiten gesprochen. Und was passiert innerhalb der Indizes?

Die Informationen auf den Indexseiten hängen stark vom jeweiligen Indextyp ab. Und selbst ein Indextyp hat unterschiedliche Seitentypen. Zum Beispiel hat der B-Baum eine Seite mit Metadaten und "regulären" Seiten.

Eine Seite hat jedoch normalerweise ein Array von Zeigern auf die Zeilen und die Zeilen selbst (genau wie auf einer Tabellenseite). Außerdem befindet sich am Ende der Seite ein Platz für spezielle Daten.

Zeilen in Indizes können je nach Indextyp auch eine sehr unterschiedliche Struktur haben. Bei einem B-Baum enthalten Zeilen, die sich auf Blattseiten beziehen, beispielsweise den Wert des Indexschlüssels und einen Link (ctid) zur entsprechenden Zeile der Tabelle. Im Allgemeinen kann ein Index ganz anders angeordnet werden.

Der wichtigste Punkt ist, dass es in keinem Indextyp Zeilenversionen gibt. Nun, oder wir können davon ausgehen, dass jede Zeile durch genau eine Version dargestellt wird. Mit anderen Worten, die Kopfzeile der Indexzeile enthält keine xmin- und xmax-Felder. Wir können davon ausgehen, dass die Links aus dem Index zu allen tabellarischen Versionen der Zeilen führen. Sie können also nur herausfinden, welche Version die Transaktion sehen wird, wenn Sie sich die Tabelle ansehen. (Wie üblich ist dies nicht die ganze Wahrheit. In einigen Fällen können Sie mit der Sichtbarkeitskarte den Prozess optimieren, wir werden dies jedoch später genauer betrachten.)

Gleichzeitig finden wir auf der Indexseite Zeiger auf beide Versionen, sowohl die aktuelle als auch die alte:

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

Virtuelle Transaktionen


In der Praxis verwendet PostgreSQL Optimierungen, um Transaktionsnummern zu „speichern“.

Wenn eine Transaktion nur Daten liest, hat dies keine Auswirkungen auf die Sichtbarkeit von Zeilenversionen. Daher gibt der Serving-Prozess zunächst eine Transaktion mit einer virtuellen Nummer (Virtual XID) aus. Die Nummer besteht aus einer Prozesskennung und einer fortlaufenden Nummer.

Die Ausgabe dieser Nummer erfordert keine Synchronisation zwischen allen Prozessen und ist daher sehr schnell. Wir werden einen weiteren Grund für die Verwendung virtueller Zahlen kennenlernen, wenn wir über das Einfrieren sprechen.

Virtuelle Nummern werden in Datenschnappschüssen nicht berücksichtigt.

Zu verschiedenen Zeitpunkten werden möglicherweise virtuelle Transaktionen mit bereits verwendeten Nummern im System angezeigt, und dies ist normal. Eine solche Nummer kann jedoch nicht auf Datenseiten geschrieben werden, da sie beim nächsten Zugriff auf die Seite möglicherweise an Bedeutung verliert.

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

Wenn die Transaktion beginnt, Daten zu ändern, erhält sie eine echte, eindeutige Transaktionsnummer.

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

 => COMMIT; 

Verschachtelte Transaktionen


Punkte speichern


SQL definiert Sicherungspunkte, mit denen Sie einen Teil einer Transaktion rückgängig machen können, ohne sie vollständig zu unterbrechen. Dies passt jedoch nicht in das obige Schema, da der Status einer Transaktion für alle Änderungen eins ist und physisch keine Daten zurückgesetzt werden.

Um eine solche Funktionalität zu implementieren, wird eine Transaktion mit einem Speicherpunkt in mehrere separate verschachtelte Transaktionen (Subtransaktion) unterteilt, deren Status separat gesteuert werden kann.

Verschachtelte Transaktionen haben eine eigene Nummer (höher als die Haupttransaktionsnummer). Der Status verschachtelter Transaktionen wird in XACT wie gewohnt aufgezeichnet. Der endgültige Status hängt jedoch vom Status der Haupttransaktion ab: Wenn diese abgebrochen wird, werden auch alle verschachtelten Transaktionen abgebrochen.

Informationen zur Verschachtelung von Transaktionen werden in Dateien im Verzeichnis PGDATA / pg_subtrans gespeichert. Der Zugriff auf Dateien erfolgt über Puffer im gemeinsam genutzten Speicher der Instanz, die wie XACT-Puffer organisiert sind.

Verwechseln Sie nicht verschachtelte Transaktionen und autonome Transaktionen. Autonome Transaktionen sind in keiner Weise voneinander abhängig, und verschachtelte Transaktionen sind abhängig. Es gibt keine autonomen Transaktionen im üblichen PostgreSQL, und vielleicht zum Besseren: Wenn sie sehr, sehr selten benötigt werden und ihre Anwesenheit in anderen DBMS Missbrauch hervorruft, unter dem dann jeder leidet.

Löschen Sie die Tabelle, starten Sie die Transaktion und fügen Sie die Zeile ein:

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

Setzen Sie nun einen Sicherungspunkt und fügen Sie eine weitere Zeile ein.

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

Beachten Sie, dass die Funktion txid_current () die Nummer der nicht verschachtelten Haupttransaktion zurückgibt.

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

Wir rollen zurück zum Speicherpunkt und fügen die dritte Zeile ein.

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

Auf der Seite sehen wir weiterhin die Zeile, die durch die abgebrochene verschachtelte Transaktion hinzugefügt wurde.

Wir korrigieren die Änderungen.

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

Jetzt können Sie deutlich sehen, dass jede verschachtelte Transaktion ihren eigenen Status hat.

Beachten Sie, dass verschachtelte Transaktionen in SQL nicht explizit verwendet werden können. Sie können also keine neue Transaktion starten, ohne die aktuelle abzuschließen. Dieser Mechanismus wird implizit bei der Verwendung von Sicherungspunkten sowie bei der Behandlung von PL / pgSQL-Ausnahmen und in einer Reihe anderer, exotischerer Fälle verwendet.

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

Fehler und Atomizität von Operationen


Was passiert, wenn während des Vorgangs ein Fehler auftritt? Zum Beispiel so:

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

Ein Fehler ist aufgetreten. Jetzt wird die Transaktion als abgebrochen betrachtet und es ist keine einzige Operation darin zulässig:

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

Und selbst wenn Sie versuchen, die Änderungen zu übernehmen, meldet PostgreSQL die Stornierung:

 => COMMIT; 
 ROLLBACK 

Warum kann ich die Transaktion nach einem Fehler nicht fortsetzen? Tatsache ist, dass ein Fehler auftreten kann, so dass wir Zugriff auf einen Teil der Änderungen erhalten - die Atomizität nicht einmal der Transaktion, aber der Operator würde verletzt. Wie in unserem Beispiel, in dem es dem Bediener gelungen ist, eine Zeile vor dem Fehler zu aktualisieren:

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

Ich muss sagen, dass es in psql einen Modus gibt, in dem Sie die Transaktion nach einem Fehler weiterhin fortsetzen können, als ob die Aktionen des fehlerhaften Operators zurückgesetzt würden.

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

Es ist leicht zu erraten, dass psql in diesem Modus tatsächlich einen impliziten Speicherpunkt vor jedem Befehl setzt und im Fehlerfall einen Rollback darauf initiiert. Dieser Modus wird standardmäßig nicht verwendet, da das Festlegen von Speicherpunkten (auch ohne ein Zurücksetzen auf diese) mit einem erheblichen Overhead verbunden ist.

Fortsetzung folgt.

Source: https://habr.com/ru/post/de445820/


All Articles