Errore PostgreSQL: canceling statement due to lock timeout
ERROR: canceling statement due to lock timeout
Lo statement ha aspettato un lock più a lungo di quanto consente lock_timeout e ha rinunciato. A differenza della maggior parte degli errori in questa lista, questo di solito significa che un meccanismo di sicurezza ha funzionato: fallire in fretta su un lock è quasi sempre meglio che accodarsi dietro di esso — specialmente per il DDL.
Cosa significa questo errore
Perché fallire in fretta conti riguarda la coda, non chi aspetta: un ALTER TABLE ha bisogno di un lock ACCESS EXCLUSIVE. Mentre aspetta dietro qualche query di lunga durata, ogni nuova query su quella tabella — inclusi semplici SELECT — si accoda dietro di esso. Una migrazione bloccata può congelare una tabella di produzione nel giro di secondi. lock_timeout converte quello scenario in un fallimento rapido e riprovabile.
Non confonderlo con statement_timeout: quello limita il tempo di esecuzione totale; lock_timeout limita una singola attesa su un lock e ha il suo SQLSTATE (55P03).
Cause comuni
- DDL dietro una transazione lunga — un report, una sessione
idle in transactionbloccata, un job batch. - Lock di riga tenuti a lungo da transazioni che fanno lavoro lento tra BEGIN e COMMIT.
- Code di lock: non stavi aspettando chi teneva il lock originale ma chi era in attesa del lock esclusivo davanti a te.
Come diagnosticarlo
-- Chi blocca chi, adesso:
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));
Imposta anche log_lock_waits = on: le attese più lunghe di deadlock_timeout vengono loggate con entrambi i lati, dandoti una storia della contesa, non solo la vista live.
Come risolverlo
- Per le migrazioni, mantieni il pattern e aggiungi i retry:
Timeout breve, loop di retry, finestra fuori dai picchi: lo standard per un DDL senza drammi.SET lock_timeout = '3s'; ALTER TABLE orders ADD COLUMN note text; -- se fallisce: riprova con backoff - Occupati di chi blocca: correggi il job che tiene aperte le transazioni; imposta
idle_in_transaction_session_timeout; come ultima risorsa deliberata,pg_terminate_backend(pid). - Preferisci le varianti non bloccanti dove esistono:
CREATE INDEX CONCURRENTLY, backfill a batch invece di un unico UPDATE gigante.
