PostgreSQL-Fehler: canceling statement due to lock timeout
ERROR: canceling statement due to lock timeout
Die Anweisung wartete länger auf einen Lock, als lock_timeout erlaubt, und gab auf. Anders als die meisten Fehler auf dieser Liste bedeutet dieser meist, dass ein Sicherheitsmechanismus funktioniert hat: bei einem Lock schnell zu scheitern ist fast immer besser, als sich dahinter anzustellen — besonders bei DDL.
Was dieser Fehler bedeutet
Warum schnelles Scheitern wichtig ist, hat mit der Warteschlange zu tun, nicht dem Wartenden: ein ALTER TABLE braucht einen ACCESS EXCLUSIVE-Lock. Während es hinter einer lange laufenden Query wartet, stellt sich jede neue Query auf dieser Tabelle — einschließlich schlichter SELECTs — hinter ihm an. Eine feststeckende Migration kann eine Produktionstabelle innerhalb von Sekunden einfrieren. lock_timeout verwandelt dieses Szenario in einen schnellen, wiederholbaren Fehlschlag.
Verwechseln Sie es nicht mit statement_timeout: das begrenzt die Gesamtlaufzeit; lock_timeout begrenzt eine einzelne Lock-Wartezeit und hat einen eigenen SQLSTATE (55P03).
Häufige Ursachen
- DDL hinter einer langen Transaktion — einem Report, einer feststeckenden
idle in transaction-Sitzung, einem Batch-Job. - Zeilen-Locks lange gehalten von Transaktionen, die zwischen BEGIN und COMMIT langsame Arbeit verrichten.
- Lock-Warteschlangen: Sie warteten nicht auf den ursprünglichen Halter, sondern auf den Exklusiv-Lock-Wartenden vor Ihnen.
So diagnostizieren Sie ihn
-- Wer wen blockiert, jetzt:
SELECT waiting.pid, waiting.query AS waiting_query,
blocking.pid AS blocking_pid, blocking.query AS blocking_query,
now() - blocking.xact_start AS blocking_xact_age
FROM pg_stat_activity waiting
JOIN pg_stat_activity blocking
ON blocking.pid = ANY (pg_blocking_pids(waiting.pid));
Setzen Sie außerdem log_lock_waits = on: Wartezeiten länger als deadlock_timeout werden mit beiden Seiten protokolliert und geben Ihnen eine Historie der Contention, nicht nur die Live-Ansicht.
So beheben Sie ihn
- Für Migrationen: behalten Sie das Muster und fügen Sie Wiederholungen hinzu:
Kurzes Timeout, Retry-Schleife, Off-Peak-Fenster: der Standard für dramafreies DDL.SET lock_timeout = '3s'; ALTER TABLE orders ADD COLUMN note text; -- bei Fehlschlag: mit Backoff erneut versuchen - Kümmern Sie sich um den Blockierer: beheben Sie den Job, der Transaktionen offen hält; setzen Sie
idle_in_transaction_session_timeout; als bewusstes letztes Mittelpg_terminate_backend(pid). - Bevorzugen Sie nicht-blockierende Varianten, wo es sie gibt:
CREATE INDEX CONCURRENTLY, gebatchte Backfills statt eines einzigen riesigen UPDATE.
