Ich habe eine Postgres 9.2-Datenbank, in der eine bestimmte Tabelle viele nicht entfernbare tote Zeilen enthält:
# SELECT * FROM public.pgstattuple('mytable');
table_len | tuple_count | tuple_len | tuple_percent | dead_tuple_count | dead_tuple_len | dead_tuple_percent | free_space | free_percent
------------+-------------+-----------+---------------+------------------+----------------+--------------------+------------+--------------
2850512896 | 283439 | 100900882 | 3.54 | 2537195 | 2666909495 | 93.56 | 50480156 | 1.77
(1 row)
Normales Staubsaugen zeigt auch viele nicht entfernbare tote Reihen:
# VACUUM VERBOSE mytable;
[...]
INFO: "mytable": found 0 removable, 2404332 nonremovable row versions in 309938 out of 316307 pages
DETAIL: 2298005 dead row versions cannot be removed yet.
There were 0 unused item pointers.
0 pages are entirely empty.
CPU 1.90s/2.05u sec elapsed 16.79 sec.
[...]
Die Tabelle enthält nur etwa 300.000 tatsächliche Datenzeilen, aber 2,3 Millionen tote Zeilen (und dies scheint bestimmte Abfragen sehr langsam zu machen).
Demnach SELECT * FROM pg_stat_activity where xact_start is not null and datname = 'mydb' order by xact_start;
gibt es keine alte Transaktion, die auf die Datenbank zugreift. Die ältesten Transaktionen sind einige Minuten alt und haben noch nichts auf dem Tisch geändert.
Ich habe auch überprüft select * from pg_prepared_xacts
(um nach vorbereiteten Transaktionen zu suchen) und select * from pg_stat_replication
(um nach ausstehenden Replikationen zu suchen), die beide leer sind.
In dieser Tabelle werden viele Einfügungen, Aktualisierungen und Löschungen durchgeführt, sodass ich verstehen kann, dass viele tote Zeilen erstellt werden. Aber warum werden sie nicht mit dem Befehl VACUUM entfernt?
quelle
Antworten:
Das reicht nicht aus. Ich denke, was erforderlich ist, um diese Zeilen als tot zu markieren, ist, dass es beim Start dieser Transaktionen keine andere Transaktion gab, die diese Zeilen berührt hat (UPDATE oder DELETE für sie).
Durch das Aktualisieren oder Löschen einer Zeile wird die vorherige Version der Zeile physisch
xmax
beibehalten und das Feld auf die TXID der aktuellen Transaktion gesetzt. Aus Sicht anderer Transaktionen ist diese alte Version der Zeile weiterhin sichtbar, wenn sie Teil ihres Snapshots ist. Jeder Schnappschuss hat einxmin
und,xmax
mit dem diexmin
undxmax
der Zeilenversionen verglichen werden können. Der Punkt ist, dass VACUUM Zeilenversionen mit der kombinierten Sichtbarkeit aller Live-Snapshots vergleichen muss, anstatt einfach zu überprüfen, ob eine Zeilenänderung definitiv festgeschrieben ist. Letzteres ist notwendig, aber nicht ausreichend, um den von der alten Version verwendeten Speicherplatz zu recyceln.Hier ist beispielsweise eine Folge von Ereignissen, sodass VACUUM keine toten Zeilen bereinigen kann, obwohl die Transaktion, die sie geändert hat, abgeschlossen wurde:
t0
: Die lang laufende Transaktion TX1 wird gestartett0+30mn
: TX2 startet und versetzt sich in den REPEATABLE READ-Modus.t0+35mn
: TX1 wird beendet.t0+40mn
: pg_stat_activity zeigt nur den 10 Minuten alten TX2 ant0+45mn
: VACUUM wird ausgeführt, entfernt jedoch nicht die alten Versionen der von TX1 geänderten Zeilen (da TX2 sie möglicherweise benötigt).quelle
REPEATABLE READ
würde nicht sofort TX2 wenn das der Fall nach TX1 „beendet“ (Commits?) Ohne Rollback bekommen istERROR: could not serialize access due to concurrent update
auf der nächsten DML (wenn txn2 nicht eine Sperre auf Zeilen nach dem txn2 Snapshot geändert bekommen)?Ich konnte das nachbauen. Im Wesentlichen, wenn innerhalb einer Transaktion,
READ COMMITTED
der Standardtransaktionsstufe:SELECT
bekommt eineAccessShareLock
VACUUM
kann Versionen für tote Zeilen bereinigenpg_stat_activity.backend_xmin IS NULL
für die TransaktionSERIALIZABLE
oderREPEATABLE READ
Transaktionsebenen:SELECT
bekommt eineAccessShareLock
VACUUM
Versionen für tote Zeilen können nicht bereinigt werdenpg_stat_activity.backend_xmin IS NOT NULL
für die TransaktionVERBOSE
meldet diese Zeilen als "nicht entfernbare Zeilenversionen" und "tote Zeilenversionen"Beispieldaten
Als Seite beachten, wenn Sie etwas aus löschen ,
bar
nachdem Sie die Tabelle erstellen, werden die Zeilenremovable
, undVACUUM
Sie werden sehen.Transaktionssequenz
Hier ist die TXN-Tabelle, um das Szenario neu zu erstellen.
VACUUM
Diese Zeilenversionen können nicht entfernt werden, da sie in einem nachfolgendenSELECT * FROM bar;
UnterREPEATABLE READ
weiterhin angezeigt werden! DasVACUUM
obige erzeugt,Welches ist genau das, was Sie sehen.
Debuggen des Problems
VACUUM
Führen Sie Folgendes aus, um herauszufinden, welche Abfrage die Bereinigung der toten Zeilen verhindert.Dies wird so etwas zurückgeben ..
Lösung
Kehren wir also zu unseren TXNs zurück. Wir müssen txn1 beenden / festschreiben / zurücksetzen und erneut ausführen
VACUUM
Und jetzt sehen wir,
Besondere Hinweise
ACCESS SHARE
Sperre für den Tisch. Und dannVACUUM
können die toten Reihen nicht entfernt werden, so dass sie als "nicht entfernbar" markiert sind.Ich denke das ist ziemlich schlechtes Benehmen für
VACUUM VERBOSE
. Ich hätte gerne gesehen ..Weiterführende Literatur
VACUUM
dort.Dieser Kommentar von @Craig Ringer, der dies zusammenfasst
Für das, was sich als eine Reise durch das Kaninchenloch herausstellen könnte, meine Frage nach weiteren Informationen,
backend_xmin
die von meiner Arbeit mit dieser Antwort inspiriert wurdenVielen Dank auch an Daniel Vérité , der mich dazu gebracht hat, den Systemkatalog und das Verhalten
VACUUM
in diesem zu untersuchen.quelle
ACCESS SHARE
Sperre für eine Tabelle hindert VACUUM nicht daran, diese Tabelle zuSHARE UPDATE EXCLUSIVE
sperren. Der Abschnitt "Was ist los?" Der Antwort hat es rückwärts, scheint mir. Auch diese andere Frage: dba.stackexchange.com/questions/21068/… ist eine gute Lektüre dafür, wie eine zu starke Verriegelung verhindern kann, dass Vakuum funktioniert, aber Vanille-Lesungen verursachen dieses Problem nicht.backend_xmin
. Ich werde diesen Abschnitt in einem Kommentar auf der Website ausblenden, weil ich damit einverstanden bin. Es ist eindeutig nicht ganz richtig. In der Zwischenzeit helfen Sie mir, diese Frage auf Interna zu beantworten: dba.stackexchange.com/q/161050/2639 =)Ich war mit diesem Problem konfrontiert, obwohl ich überprüft hatte, dass meine Datenbank keine aktive Transaktion oder aktive Sperre für eine bestimmte "foo" -Tabelle hatte.
Mit der folgenden Methode wurden alle nicht entfernbaren toten Zeilen erfolgreich aus "foo" entfernt:
Beachten Sie jedoch, dass eine große Tabelle mit zu vielen Zeilen möglicherweise keine praktikable Lösung darstellt, da alle Tabellenzeilen in eine temporäre Tabelle und dann zurück in die ursprüngliche Tabelle übertragen werden.
quelle