it-swarm.com.de

VACUUM VERBOSE-Ausgänge, nicht entfernbare "Dead Row-Versionen können noch nicht entfernt werden"?

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

Gemäß SELECT * FROM pg_stat_activity where xact_start is not null and datname = 'mydb' order by xact_start; Es gibt 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 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?

8
oliver

Die ältesten Transaktionen sind einige Minuten alt und haben noch nichts auf dem Tisch geändert.

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 beibehalten, und das Feld xmax wird 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 Snapshot hat ein xmin und xmax, mit denen die xmin und xmax 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 gestartet
  • t0+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 an
  • t0+45mn: VACUUM wird ausgeführt, entfernt jedoch nicht die alten Versionen der von TX1 geänderten Zeilen (da TX2 sie möglicherweise benötigt).
7
Daniel Vérité

Ich konnte das nachbauen. Im Wesentlichen, wenn innerhalb einer Transaktion,

  • Im READ COMMITTED die Standardtransaktionsstufe:
  • In SERIALIZABLE oder REPEATABLE READ Transaktionsebenen:
    • SELECT erhält ein AccessShareLock
    • VACUUM kann nicht Versionen für tote Zeilen bereinigen
    • pg_stat_activity.backend_xmin IS NOT NULL für die Transaktion
    • VERBOSE meldet diese Zeilen als "nicht entfernbare Zeilenversionen" und "tote Zeilenversionen"

Beispieldaten

CREATE TABLE bar AS
SELECT x::int FROM generate_series(1,10) AS t(x);

Wenn Sie nach dem Erstellen der Tabelle etwas aus bar löschen, werden diese Zeilen zu removable, und auf VACUUM werden Sie sehen.

INFO:  "bar": removed # row versions in # pages

Transaktionssequenz

Hier ist die TXN-Tabelle, um das Szenario neu zu erstellen.

txn1       - BEGIN; SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
txn1       - SELECT * FROM bar;
      txn2 - DELETE FROM bar;      -- We delete after the select
      txn2 - VACUUM VERBOSE bar;   -- Can't remove the "dead row versions"

VACUUM kann diese Zeilenversionen nicht entfernen, da ein nachfolgendes SELECT * FROM bar; unter REPEATABLE READ wird sie noch sehen! Das obige VACUUM erzeugt,

# VACUUM VERBOSE bar;
INFO:  vacuuming "public.bar"
INFO:  "bar": found 0 removable, 10 nonremovable row versions in 1 out of 1 pages
DETAIL:  10 dead row versions cannot be removed yet.
There were 0 unused item pointers.
Skipped 0 pages due to buffer pins.
0 pages are entirely empty.
CPU 0.00s/0.00u sec elapsed 0.00 sec.

Welches ist genau das, was Sie sehen.

Debuggen des Problems

Führen Sie Folgendes aus, um herauszufinden, welche Abfrage verhindert, dass VACUUM die toten Zeilen bereinigt.

SELECT query, state,locktype,mode
FROM pg_locks
JOIN pg_stat_activity
  USING (pid)
WHERE relation::regclass = 'bar'::regclass
  AND granted IS TRUE
  AND backend_xmin IS NOT NULL;

Dies wird so etwas zurückgeben ..

       query        │        state        │ locktype │      mode       
────────────────────┼─────────────────────┼──────────┼─────────────────
 SELECT * FROM bar; │ idle in transaction │ relation │ AccessShareLock

Lösung

Kehren wir also zu unseren TXNs zurück. Wir müssen txn1 beenden/festschreiben/zurücksetzen und VACUUM erneut ausführen

txn1       - COMMIT;
      txn2 - VACUUM VERBOSE bar;

Und jetzt sehen wir,

# VACUUM VERBOSE bar;
INFO:  vacuuming "public.bar"
INFO:  "bar": removed 10 row versions in 1 pages
INFO:  "bar": found 10 removable, 0 nonremovable row versions in 1 out of 1 pages
DETAIL:  0 dead row versions cannot be removed yet.
There were 0 unused item pointers.
Skipped 0 pages due to buffer pins.
0 pages are entirely empty.
CPU 0.00s/0.00u sec elapsed 0.00 sec.
INFO:  "bar": truncated 1 to 0 pages
DETAIL:  CPU 0.00s/0.00u sec elapsed 0.01 sec.

Besondere Hinweise

  1. Es spielt keine Rolle, welche Zeilen gelöscht wurden und für welche Zeilen Sie ausgewählt haben. Die Auswahl erhält das ACCESS SHARE Tisch sperren. Und dann kann VACUUM die toten Zeilen nicht entfernen, sodass sie als "nicht entfernbar" markiert sind.
  2. Ich denke, das ist ein ziemlich schlechtes Verhalten für VACUUM VERBOSE. Ich hätte gerne gesehen ..

    DETAIL:  10 dead row versions cannot be removed yet
             could not aquire SHARE UPDATE EXCLUSIVE lock on %TABLE
    

Weiterführende Literatur

Danke auch an Daniel Vérité , der mich dazu gebracht hat, in den Systemkatalog und das Verhalten von VACUUM in diesem zu schauen.

6
Evan Carroll

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:

CREATE TEMP TABLE temp_foo AS SELECT * FROM "foo";
TRUNCATE TABLE "foo";
INSERT INTO "foo" SELECT * FROM temp_foo;
DROP table temp_foo;

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.