Wir begannen mit Problemen im Zusammenhang mit der
Isolation , machten einen Exkurs über das
Organisieren von Daten auf niedriger Ebene und sprachen ausführlich
über Zeilenversionen und wie
Snapshots aus Versionen erhalten werden.
Dann untersuchten wir verschiedene Arten der Reinigung:
Intra-Page (zusammen mit HOT-Updates),
regelmäßig und
automatisch .
Und bin zum letzten Thema dieses Zyklus gekommen. Heute werden wir über das Problem des Umlaufens und Einfrierens von Transaktions-IDs sprechen.
Transaktionszählerüberlauf
PostgreSQL verfügt über 32 Bits für die Transaktionsnummer. Dies ist eine ziemlich große Zahl (ungefähr 4 Milliarden), aber mit dem aktiven Betrieb des Servers kann es durchaus erschöpft sein. Bei einer Last von 1000 Transaktionen pro Sekunde geschieht dies beispielsweise nach nur anderthalb Monaten Dauerbetrieb.
Wir haben jedoch darüber gesprochen, dass der Multi-Versionierungsmechanismus auf der Reihenfolge der Nummerierung beruht. Von zwei Transaktionen kann eine Transaktion mit einer niedrigeren Nummer als früher gestartet betrachtet werden. Daher ist es klar, dass Sie den Zähler nicht einfach zurücksetzen und die Nummerierung erneut fortsetzen können.

Warum werden der Transaktionsnummer keine 64 Bit zugewiesen, da dies das Problem vollständig beseitigen würde? Tatsache ist, dass (wie
bereits erwähnt ) im Header jeder Version der Zeile zwei Transaktionsnummern gespeichert sind - xmin und xmax. Der Header ist bereits ziemlich groß, mindestens 23 Bytes, und eine Erhöhung der Bittiefe würde zu einer Erhöhung um weitere 8 Bytes führen. Das ist absolut unmöglich.
64-Bit-Transaktionsnummern sind im Produkt unseres Unternehmens Postgres Pro Enterprise implementiert, aber auch dort sind sie nicht ganz ehrlich: xmin und xmax bleiben 32-Bit, und die Überschrift der Seite enthält einen allgemeinen "Beginn einer Ära" auf der gesamten Seite.
Was tun? Anstelle eines linearen Diagramms werden alle Transaktionsnummern wiederholt. Bei jeder Transaktion wird davon ausgegangen, dass die Hälfte der Zahlen „gegen den Uhrzeigersinn“ zur Vergangenheit und die Hälfte „im Uhrzeigersinn“ zur Zukunft gehört.
Das Alter einer Transaktion ist die Anzahl der Transaktionen, die seit ihrem Erscheinen im System bestanden wurden (unabhängig davon, ob der Zähler Null durchlaufen hat oder nicht). Wenn wir verstehen wollen, ob eine Transaktion älter als eine andere ist oder nicht, vergleichen wir ihr Alter und nicht ihre Zahlen. (Daher sind die Operationen "größer" und "kleiner" übrigens nicht für den Datentyp xid definiert.)

In einer solchen Schleifenschaltung entsteht jedoch eine unangenehme Situation. Eine Transaktion, die in der fernen Vergangenheit war (Transaktion 1 in der Abbildung), befindet sich nach einer Weile in der Hälfte des Kreises, der sich auf die Zukunft bezieht. Dies verstößt natürlich gegen die Sichtbarkeitsregeln und würde zu Problemen führen - die durch Transaktion 1 vorgenommenen Änderungen würden einfach aus dem Blickfeld verschwinden.

Versionsfrieren und Sichtbarkeitsregeln
Um solche „Reisen“ von der Vergangenheit in die Zukunft zu verhindern, führt der Reinigungsprozess (zusätzlich zur Freigabe von Speicherplatz auf den Seiten) eine weitere Aufgabe aus. Er findet ziemlich alte und "kalte" Versionen der Linien (die auf allen Bildern sichtbar sind und deren Änderung bereits unwahrscheinlich ist) und markiert sie auf besondere Weise - "friert" sie ein. Die eingefrorene Version der Zeile gilt als älter als alle regulären Daten und ist in allen Datenschnappschüssen immer sichtbar. Darüber hinaus muss die Transaktionsnummer xmin nicht mehr angezeigt werden, und diese Nummer kann sicher wiederverwendet werden. So bleiben eingefrorene Versionen von Strings immer in der Vergangenheit.

Um die Transaktionsnummer xmin als eingefroren zu markieren, werden beide Hinweisbits gleichzeitig gesetzt - das Festschreibungsbit und das Abbruchbit.
Beachten Sie, dass die xmax-Transaktion nicht eingefroren werden muss. Aufgrund seiner Anwesenheit ist diese Version der Zeichenfolge nicht mehr relevant. Nachdem es in Datenschnappschüssen nicht mehr sichtbar ist, wird diese Version der Zeile gelöscht.
Erstellen Sie für Experimente eine Tabelle. Wir stellen den minimalen Füllfaktor so ein, dass nur zwei Zeilen auf jede Seite passen. So können wir bequemer beobachten, was passiert. Schalten Sie die Automatisierung aus, um die Reinigungszeit selbst zu steuern.
=> CREATE TABLE tfreeze( id integer, s char(300) ) WITH (fillfactor = 10, autovacuum_enabled = off);
Wir haben bereits mehrere Varianten der Funktion erstellt, die mithilfe der Erweiterung pageinspect die Version der Zeilen auf der Seite anzeigen. Jetzt erstellen wir eine weitere Variante derselben Funktion: Jetzt werden mehrere Seiten gleichzeitig angezeigt und das Alter der xmin-Transaktion angezeigt (hierfür wird das Alter der Systemfunktion verwendet):
=> CREATE FUNCTION heap_page(relname text, pageno_from integer, pageno_to integer) RETURNS TABLE(ctid tid, state text, xmin text, xmin_age integer, 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+512) = 256+512 THEN ' (f)' WHEN (t_infomask & 256) > 0 THEN ' (c)' WHEN (t_infomask & 512) > 0 THEN ' (a)' ELSE '' END AS xmin, age(t_xmin) xmin_age, t_xmax || CASE WHEN (t_infomask & 1024) > 0 THEN ' (c)' WHEN (t_infomask & 2048) > 0 THEN ' (a)' ELSE '' END AS xmax, t_ctid FROM generate_series(pageno_from, pageno_to) p(pageno), heap_page_items(get_raw_page(relname, pageno)) ORDER BY pageno, lp; $$ LANGUAGE SQL;
Bitte beachten Sie, dass das Zeichen des Einfrierens (das wir mit dem Buchstaben f in Klammern anzeigen) durch die gleichzeitige Installation festgeschriebener und abgebrochener Eingabeaufforderungen bestimmt wird. Viele Quellen (einschließlich Dokumentation) erwähnen die spezielle Nummer FrozenTransactionId = 2, die eingefrorene Transaktionen kennzeichnet. Ein solches System funktionierte bis Version 9.4, wurde jedoch jetzt durch Tooltip-Bits ersetzt. Auf diese Weise können Sie die ursprüngliche Transaktionsnummer in der Zeilenversion speichern, was für Support- und Debugging-Zwecke praktisch ist. Transaktionen mit der Nummer 2 können jedoch weiterhin in älteren Systemen erfolgen, selbst wenn sie auf die neuesten Versionen aktualisiert wurden.
Wir benötigen auch die Erweiterung pg_visibility, mit der Sie in die Sichtbarkeitskarte schauen können:
=> CREATE EXTENSION pg_visibility;
Vor PostgreSQL 9.6 enthielt die Sichtbarkeitskarte ein Bit pro Seite. Es wurden Seiten markiert, die nur „ziemlich alte“ Versionen von Zeichenfolgen enthalten, die garantiert bereits in allen Bildern sichtbar sind. Die Idee hier ist, dass, wenn die Seite in der Sichtbarkeitskarte markiert ist, Sie für ihre Version der Linien die Sichtbarkeitsregeln nicht überprüfen müssen.
Ab Version 9.6 wurde derselben Ebene eine Freeze-Map hinzugefügt - ein weiteres Bit pro Seite. Die Freeze Map markiert die Seiten, auf denen alle Versionen der Zeilen eingefroren sind.
Wir fügen mehrere Zeilen in die Tabelle ein und führen sofort die Reinigung durch, um eine Sichtbarkeitskarte zu erstellen:
=> INSERT INTO tfreeze(id, s) SELECT g.id, 'FOO' FROM generate_series(1,100) g(id); => VACUUM tfreeze;
Und wir sehen, dass beide Seiten jetzt in der Sichtbarkeitskarte markiert sind (all_visible), aber noch nicht eingefroren sind (all_frozen):
=> SELECT * FROM generate_series(0,1) g(blkno), pg_visibility_map('tfreeze',g.blkno) ORDER BY g.blkno;
blkno | all_visible | all_frozen -------+-------------+------------ 0 | t | f 1 | t | f (2 rows)
Das Alter der Transaktion, die die Zeilen erstellt hat (xmin_age), ist 1 - dies ist die letzte Transaktion, die auf dem System ausgeführt wurde:
=> SELECT * FROM heap_page('tfreeze',0,1);
ctid | state | xmin | xmin_age | xmax | t_ctid -------+--------+---------+----------+-------+-------- (0,1) | normal | 697 (c) | 1 | 0 (a) | (0,1) (0,2) | normal | 697 (c) | 1 | 0 (a) | (0,2) (1,1) | normal | 697 (c) | 1 | 0 (a) | (1,1) (1,2) | normal | 697 (c) | 1 | 0 (a) | (1,2) (4 rows)
Mindestalter zum Einfrieren
Drei Hauptparameter steuern das Einfrieren, und wir werden sie der Reihe nach betrachten.
Beginnen wir mit
vakuum_freeze_min_age , das das minimale Transaktionsalter xmin definiert, bei dem die Zeichenfolgenversion eingefroren werden kann. Je niedriger dieser Wert ist, desto unnötiger können sich die Gemeinkosten herausstellen: Wenn es sich um „heiße“, sich aktiv ändernde Daten handelt, ist das Einfrieren von immer mehr neuen Versionen nutzlos. In diesem Fall ist es besser zu warten.
Der Standardwert für diesen Parameter legt fest, dass Transaktionen einfrieren, nachdem 50 Millionen andere Transaktionen seit ihrem Erscheinen vergangen sind:
=> SHOW vacuum_freeze_min_age;
vacuum_freeze_min_age ----------------------- 50000000 (1 row)
Um zu sehen, wie es zum Einfrieren kommt, reduzieren wir den Wert dieses Parameters auf Eins.
=> ALTER SYSTEM SET vacuum_freeze_min_age = 1; => SELECT pg_reload_conf();
Und wir aktualisieren eine Zeile auf der Nullseite. Die neue Version wird aufgrund des kleinen Füllfaktorwerts auf dieselbe Seite gelangen.
=> UPDATE tfreeze SET s = 'BAR' WHERE id = 1;
Folgendes sehen wir jetzt auf den Datenseiten:
=> SELECT * FROM heap_page('tfreeze',0,1);
ctid | state | xmin | xmin_age | xmax | t_ctid -------+--------+---------+----------+-------+-------- (0,1) | normal | 697 (c) | 2 | 698 | (0,3) (0,2) | normal | 697 (c) | 2 | 0 (a) | (0,2) (0,3) | normal | 698 | 1 | 0 (a) | (0,3) (1,1) | normal | 697 (c) | 2 | 0 (a) | (1,1) (1,2) | normal | 697 (c) | 2 | 0 (a) | (1,2) (5 rows)
Jetzt sollen Zeilen, die älter als
vakuum_freeze_min_age = 1 sind, eingefroren werden. Beachten Sie jedoch, dass die Nulllinie in der Sichtbarkeitskarte nicht markiert ist (das Bit wurde durch den Befehl UPDATE zurückgesetzt, der die Seite geändert hat), und die erste bleibt aktiviert:
=> SELECT * FROM generate_series(0,1) g(blkno), pg_visibility_map('tfreeze',g.blkno) ORDER BY g.blkno;
blkno | all_visible | all_frozen -------+-------------+------------ 0 | f | f 1 | t | f (2 rows)
Wir
haben bereits gesagt, dass beim Reinigen nur Seiten gescannt werden, die nicht in der Sichtbarkeitskarte markiert sind. Und so stellt sich heraus:
=> VACUUM tfreeze; => SELECT * FROM heap_page('tfreeze',0,1);
ctid | state | xmin | xmin_age | xmax | t_ctid -------+---------------+---------+----------+-------+-------- (0,1) | redirect to 3 | | | | (0,2) | normal | 697 (f) | 2 | 0 (a) | (0,2) (0,3) | normal | 698 (c) | 1 | 0 (a) | (0,3) (1,1) | normal | 697 (c) | 2 | 0 (a) | (1,1) (1,2) | normal | 697 (c) | 2 | 0 (a) | (1,2) (5 rows)
Auf der Nullseite ist eine Version eingefroren, auf der ersten Seite wurde jedoch überhaupt keine Reinigung in Betracht gezogen. Wenn also nur aktuelle Versionen auf der Seite verbleiben, wird die Bereinigung einer solchen Seite nicht durchgeführt und sie werden nicht eingefroren.
=> SELECT * FROM generate_series(0,1) g(blkno), pg_visibility_map('tfreeze',g.blkno) ORDER BY g.blkno;
blkno | all_visible | all_frozen -------+-------------+------------ 0 | t | f 1 | t | f (2 rows)
Alter, um den gesamten Tisch einzufrieren
Um die Version der Zeilen auf den Seiten, die bei der Bereinigung noch nicht
angezeigt werden, weiterhin einzufrieren, wird ein zweiter Parameter bereitgestellt:
vakuum_freeze_table_age . Es bestimmt das Alter der Transaktion, bei dem die Bereinigung die Sichtbarkeitskarte ignoriert und alle Seiten der Tabelle durchläuft, um einzufrieren.
In jeder Tabelle ist eine Transaktionsnummer gespeichert, für die bekannt ist, dass alle älteren Transaktionen garantiert eingefroren sind (pg_class.relfrozenxid). Mit dem Alter dieser
gespeicherten Transaktion wird der Wert des Parameters
vaca_freeze_table_age verglichen .
=> SELECT relfrozenxid, age(relfrozenxid) FROM pg_class WHERE relname = 'tfreeze';
relfrozenxid | age --------------+----- 694 | 5 (1 row)
Vor PostgreSQL 9.6 führte die Bereinigung einen vollständigen Tabellenscan durch, um sicherzustellen, dass alle Seiten gecrawlt wurden. Bei großen Tischen war diese Operation lang und traurig. Die Angelegenheit wurde durch die Tatsache verschärft, dass, wenn die Reinigung nicht das Ende erreichte (zum Beispiel ein ungeduldiger Administrator die Ausführung eines Befehls unterbrach), es notwendig war, von vorne zu beginnen.
Ab Version 9.6 umgeht das Löschen dank der Freeze-Map (die wir in der Spalte all_frozen in der Ausgabe von pg_visibility_map sehen) nur die Seiten, die noch nicht in der Map markiert sind. Dies ist nicht nur ein viel geringerer Arbeitsaufwand, sondern auch ein Widerstand gegen Unterbrechungen: Wenn der Reinigungsprozess gestoppt und neu gestartet wird, muss er nicht erneut auf die Seiten schauen, die er bereits beim letzten Mal in der Gefrierkarte markiert hat.
Auf die eine oder andere Weise werden alle Seiten in der Tabelle einmalig in Transaktionen (
vakuum_freeze_table_age -
vakuum_freeze_min_age ) eingefroren. Bei Standardwerten geschieht dies einmal pro Million Transaktionen:
=> SHOW vacuum_freeze_table_age;
vacuum_freeze_table_age ------------------------- 150000000 (1 row)
Somit ist es klar, dass nicht zu viel
Vakuum-Frost-Min_age gesetzt werden sollte, da dies den Overhead erhöht, anstatt ihn zu reduzieren.
Lassen Sie uns sehen, wie die gesamte Tabelle eingefroren wird, und reduzieren Sie dazu
vakuum_freeze_table_age auf 5, damit die Bedingung für das Einfrieren erfüllt ist.
=> ALTER SYSTEM SET vacuum_freeze_table_age = 5; => SELECT pg_reload_conf();
Lassen Sie uns reinigen:
=> VACUUM tfreeze;
Da nun garantiert wurde, dass die gesamte Tabelle überprüft wird, kann die Anzahl der eingefrorenen Transaktionen erhöht werden. Wir sind sicher, dass die Seiten keine ältere, nicht eingefrorene Transaktion enthalten.
=> SELECT relfrozenxid, age(relfrozenxid) FROM pg_class WHERE relname = 'tfreeze';
relfrozenxid | age --------------+----- 698 | 1 (1 row)
Jetzt sind alle Versionen der Zeilen auf der ersten Seite eingefroren:
=> SELECT * FROM heap_page('tfreeze',0,1);
ctid | state | xmin | xmin_age | xmax | t_ctid -------+---------------+---------+----------+-------+-------- (0,1) | redirect to 3 | | | | (0,2) | normal | 697 (f) | 2 | 0 (a) | (0,2) (0,3) | normal | 698 (c) | 1 | 0 (a) | (0,3) (1,1) | normal | 697 (f) | 2 | 0 (a) | (1,1) (1,2) | normal | 697 (f) | 2 | 0 (a) | (1,2) (5 rows)
Außerdem ist die erste Seite in der Freeze Map markiert:
=> SELECT * FROM generate_series(0,1) g(blkno), pg_visibility_map('tfreeze',g.blkno) ORDER BY g.blkno;
blkno | all_visible | all_frozen -------+-------------+------------ 0 | t | f 1 | t | t (2 rows)
Alter für "aggressive" Reaktion
Es ist wichtig, dass Zeilenversionen rechtzeitig einfrieren. Wenn eine Situation auftritt, in der eine noch nicht eingefrorene Transaktion in die Zukunft eintreten könnte, stürzt PostgreSQL ab, um potenzielle Probleme zu vermeiden.
Was könnte der Grund dafür sein? Es gibt verschiedene Gründe.
- Die automatische Reinigung kann deaktiviert werden, und die regelmäßige Reinigung wird ebenfalls nicht gestartet. Wir haben bereits gesagt, dass dies nicht notwendig ist, aber technisch ist es möglich.
- Selbst die enthaltene automatische Bereinigung kommt nicht zu Datenbanken, die nicht verwendet werden (denken Sie an den Parameter track_counts und die Datenbank template0).
- Wie wir beim letzten Mal gesehen haben , überspringt die Reinigung Tabellen, in denen Daten nur hinzugefügt, aber nicht gelöscht oder geändert werden.
In solchen Fällen wird ein "aggressiver"
automatischer Reinigungsvorgang bereitgestellt, der durch den Parameter
autovacuum_freeze_max_age geregelt wird. Wenn in einer Tabelle einer Datenbank eine nicht eingefrorene Transaktion vorhanden ist, die älter als das im Parameter angegebene Alter ist, wird die automatische Bereinigung zwangsweise gestartet (auch wenn sie deaktiviert ist) und erreicht früher oder später die Problemtabelle (trotz der üblichen Kriterien).
Der Standardwert ist ziemlich konservativ:
=> SHOW autovacuum_freeze_max_age;
autovacuum_freeze_max_age --------------------------- 200000000 (1 row)
Das Limit für
autovacuum_freeze_max_age beträgt 2 Milliarden Transaktionen, und es wird ein zehnmal kleinerer Wert verwendet. Dies ist sinnvoll: Wenn Sie den Wert erhöhen, erhöhen Sie das Risiko, dass die automatische Reinigung für die verbleibende Zeit keine Zeit hat, alle erforderlichen Versionen der Leitungen einzufrieren.
Darüber hinaus bestimmt der Wert dieses Parameters die Größe der XACT-Struktur: Da das System keine älteren Transaktionen enthalten sollte, für die Sie möglicherweise den Status ermitteln müssen, werden durch die automatische Bereinigung unnötige XACT-Segmentdateien entfernt, wodurch Speicherplatz frei wird.
Lassen Sie uns am Beispiel von tfreeze sehen, wie die Reinigung Tabellen zum Anhängen handhabt. Für diese Tabelle ist die automatische Reinigung im Allgemeinen deaktiviert, dies ist jedoch kein Hindernis.
Das Ändern des Parameters
autovacuum_freeze_max_age erfordert einen Neustart des Servers. Alle oben diskutierten Parameter können aber auch auf der Ebene einzelner Tabellen mithilfe von Speicherparametern festgelegt werden. In der Regel ist dies nur in besonderen Fällen sinnvoll, wenn der Tisch wirklich besondere Pflege erfordert.
Also setzen wir
autovacuum_freeze_max_age auf Tabellenebene (und geben gleichzeitig auch den normalen Füllfaktor zurück). Leider ist der minimal mögliche Wert 100.000:
=> ALTER TABLE tfreeze SET (autovacuum_freeze_max_age = 100000, fillfactor = 100);
Leider, weil wir 100.000 Transaktionen abschließen müssen, um die Situation zu reproduzieren, die uns interessiert. Aber aus praktischen Gründen ist dies natürlich ein sehr, sehr niedriger Wert.
Da wir Daten hinzufügen werden, werden wir 100.000 Zeilen in die Tabelle einfügen - jede in unserer Transaktion. Und wieder muss ich reservieren, dass dies in der Praxis nicht gemacht werden sollte. Aber jetzt erforschen wir nur, wir können.
=> CREATE PROCEDURE foo(id integer) AS $$ BEGIN INSERT INTO tfreeze VALUES (id, 'FOO'); COMMIT; END; $$ LANGUAGE plpgsql; => DO $$ BEGIN FOR i IN 101 .. 100100 LOOP CALL foo(i); END LOOP; END; $$;
Wie wir sehen können, hat das Alter der letzten eingefrorenen Transaktion in der Tabelle den Schwellenwert überschritten:
=> SELECT relfrozenxid, age(relfrozenxid) FROM pg_class WHERE relname = 'tfreeze';
relfrozenxid | age --------------+-------- 698 | 100006 (1 row)
Wenn Sie jetzt etwas warten, wird im Nachrichtenprotokoll des Servers ein Eintrag über das automatische aggressive Vakuum der Tabelle "test.public.tfreeze" angezeigt. Die Nummer der eingefrorenen Transaktion ändert sich und ihr Alter kehrt zum Anstand zurück:
=> SELECT relfrozenxid, age(relfrozenxid) FROM pg_class WHERE relname = 'tfreeze';
relfrozenxid | age --------------+----- 100703 | 3 (1 row)
Es gibt auch so etwas wie das Einfrieren von Multi-Transaktionen, aber wir werden noch nicht darüber sprechen - wir werden es verschieben, bis wir über Sperren sprechen, um nicht vor uns selbst zu kommen.
Manuelles Einfrieren
Manchmal ist es bequem, das Einfrieren manuell zu steuern, anstatt auf die automatische Reinigung zu warten.
Sie können einen Befehl manuell mit dem Befehl VACUUM FREEZE einfrieren. Alle Zeilenversionen werden eingefroren, unabhängig vom Alter der Transaktionen (als ob der Parameter
autovacuum_freeze_min_age = 0 wäre). Wenn eine Tabelle mit den Befehlen VACUUM FULL oder CLUSTER neu erstellt wird, werden auch alle Zeilen eingefroren.
Um alle Datenbanken einzufrieren, können Sie das Dienstprogramm verwenden:
vacuumdb --all --freeze
Daten können auch während des ersten Ladens mit dem Befehl COPY eingefroren werden, indem der Parameter FREEZE angegeben wird. Dazu muss die Tabelle in derselben erstellt (oder mit dem Befehl TRUNCATE geleert) werden
Transaktionen als KOPIE.
Da es separate Sichtbarkeitsregeln für eingefrorene Zeilen gibt, werden diese Zeilen in Snapshots von Daten aus anderen Transaktionen angezeigt, die gegen die üblichen Isolationsregeln verstoßen (dies gilt für Transaktionen mit der Stufe "Wiederholbares Lesen" oder "Serialisierbar").
Um dies zu überprüfen, starten Sie in einer anderen Sitzung eine Transaktion mit der Isolationsstufe Wiederholbares Lesen:
| => BEGIN ISOLATION LEVEL REPEATABLE READ; | => SELECT txid_current();
Beachten Sie, dass diese Transaktion einen Snapshot der Daten erstellt hat, jedoch nicht auf die tfreeze-Tabelle zugegriffen hat. Jetzt leeren wir die tfreeze-Tabelle und laden in einer Transaktion neue Zeilen hinein. Wenn eine parallele Transaktion den Inhalt von tfreeze liest, wird der Befehl TRUNCATE bis zum Ende der Transaktion gesperrt.
=> BEGIN; => TRUNCATE tfreeze; => COPY tfreeze FROM stdin WITH FREEZE;
1 FOO 2 BAR 3 BAZ \.
=> COMMIT;
Jetzt sieht eine parallele Transaktion neue Daten, obwohl dies die Isolation unterbricht:
| => SELECT count(*) FROM tfreeze;
| count | ------- | 3 | (1 row)
| => COMMIT;
Da es jedoch unwahrscheinlich ist, dass solche Daten regelmäßig geladen werden, ist dies normalerweise kein Problem.
Bezeichnenderweise funktioniert COPY WITH FREEZE nicht mit der Sichtbarkeitskarte. Geladene Seiten enthalten nicht nur Versionen der Linien, die für alle sichtbar sind. Wenn Sie zum ersten Mal auf die Tabelle zugreifen, muss die Bereinigung daher alles erneut verarbeiten und eine Sichtbarkeitskarte erstellen. Um die Sache noch schlimmer zu machen, haben Datenseiten ein Zeichen der vollständigen Sichtbarkeit in ihrem eigenen Header, sodass beim Bereinigen nicht nur die gesamte Tabelle gelesen, sondern auch vollständig neu geschrieben wird, wobei das gewünschte Bit abgelegt wird. Leider muss die Lösung für dieses Problem nicht früher als Version 13 warten (
Diskussion ).
Fazit
Damit ist meine Artikelserie über PostgreSQL-Isolation und Multiversion abgeschlossen. Vielen Dank für Ihre Aufmerksamkeit und insbesondere für die Kommentare - sie verbessern das Material und weisen oft auf Bereiche hin, die meinerseits mehr Aufmerksamkeit erfordern.
Bleib bei uns, um fortzufahren!