PostgreSQL: database is not accepting commands to avoid wraparound data loss
ERROR: database is not accepting commands to avoid wraparound data loss in database "appdb"
HINT: Stop the postmaster and vacuum that database in single-user mode.
Dies ist die Notbremse von PostgreSQL MVCC — und falls Sie dies während des Notfalls lesen: die Lösung ist, alles zu entfernen, was das Vacuum blockiert, und dann VACUUM die ältesten Tabellen freezen zu lassen. Beachten Sie, dass der klassische HINT über den Single-User-Modus auf modernen Versionen weithin als veralteter Rat gilt; ein normales VACUUM erledigt die Aufgabe meist besser und sicherer.
Was dieser Fehler bedeutet
Transaktions-IDs (XIDs) sind 32-Bit-Werte, die aus einem Zähler zugewiesen werden, der sich im Kreis herumdreht. Damit die Zeilensichtbarkeit korrekt bleibt, darf keine lebende Zeile älter als etwa 2 Milliarden Transaktionen sein — daher freezt VACUUM kontinuierlich alte Zeilen, markiert sie als für-immer-sichtbar und gewinnt ihr Alter zurück. Wenn das Freezing weit genug zurückfällt, warnt PostgreSQL zuerst (database "appdb" must be vacuumed within N transactions) und hört dann ganz auf, datenmodifizierende Befehle anzunehmen, ein Sicherheitspuffer, bevor ein tatsächlicher Wraparound die Sichtbarkeit beschädigen würde.
Die entscheidende Erkenntnis: dies ist praktisch nie allein "das Vacuum war zu langsam" — etwas hat den Freeze-Horizont zurückgehalten. Finden Sie es zuerst; ein VACUUM, das gegen einen unbeweglichen Horizont anrennt, erreicht nichts.
Häufige Ursachen
- Eine sehr alte offene Transaktion, die den Horizont anheftet (eine Sitzung, die tage- oder wochenlang
idle in transactionist). - Verwaiste Prepared Transactions — durch Two-Phase-Commit erzeugt, für immer vergessen, bei flüchtiger Inspektion unsichtbar.
- Veraltete Replikations-Slots (mit Feedback), die den Horizont im Auftrag einer Replika halten, die nicht mehr existiert.
- Autovacuum deaktiviert oder chronisch ausgehungert auf riesigen Tabellen.
So diagnostizieren Sie ihn
-- Wie alt die Datenbank / die schlimmsten Tabellen sind:
SELECT datname, age(datfrozenxid) FROM pg_database ORDER BY 2 DESC;
SELECT relname, age(relfrozenxid) FROM pg_class
WHERE relkind = 'r' ORDER BY 2 DESC LIMIT 20;
-- Die drei üblichen Schuldigen des blockierten Horizonts:
SELECT pid, now() - xact_start AS age, state, query
FROM pg_stat_activity WHERE xact_start IS NOT NULL ORDER BY xact_start LIMIT 5;
SELECT * FROM pg_prepared_xacts;
SELECT slot_name, active, xmin FROM pg_replication_slots;
So beheben Sie ihn
- Entfernen Sie die Horizont-Blockierer: beenden Sie die uralten Transaktionen,
COMMIT PREPARED/ROLLBACK PREPAREDdie verwaisten Two-Phase-Transaktionen, droppen Sie tote Replikations-Slots. - Vacuumen Sie zuerst die schlimmsten Übeltäter: führen Sie
VACUUM (VERBOSE)auf den Tabellen mit dem höchstenrelfrozenxid-Alter aus (oder datenbankweit, wenn die Zeit reicht). Mit entfernten Blockierern sinkt das Alter und die Datenbank nimmt wieder Schreibvorgänge an, sobald sie wieder unter der Sicherheitsschwelle ist. - Danach: machen Sie es strukturell: überwachen Sie
age(datfrozenxid)mit Alarmen weit unter der 2-Milliarden-Obergrenze, halten Sie Autovacuum an und angemessen ausgestattet, und alarmieren Sie bei Prepared Transactions und inaktiven Slots — den stillen Horizont-Haltern.
