Error de PostgreSQL: canceling statement due to lock timeout
ERROR: canceling statement due to lock timeout
La sentencia esperó un bloqueo más tiempo del que permite lock_timeout y se rindió. A diferencia de la mayoría de errores de esta lista, este normalmente significa que un mecanismo de seguridad funcionó: fallar rápido ante un bloqueo casi siempre es mejor que encolarse detrás de él — especialmente para el DDL.
Qué significa este error
Por qué importa fallar rápido tiene que ver con la cola, no con el que espera: un ALTER TABLE necesita un bloqueo ACCESS EXCLUSIVE. Mientras espera detrás de alguna consulta de larga duración, cada nueva consulta sobre esa tabla — incluidos los SELECT simples — se encola detrás de él. Una migración atascada puede congelar una tabla de producción en segundos. lock_timeout convierte ese escenario en un fallo rápido y reintentable.
No lo confundas con statement_timeout: ese acota el tiempo total de ejecución; lock_timeout acota una sola espera de bloqueo y tiene su propio SQLSTATE (55P03).
Causas comunes
- DDL detrás de una transacción larga — un informe, una sesión atascada en
idle in transaction, un trabajo por lotes. - Bloqueos de fila sostenidos mucho tiempo por transacciones que hacen trabajo lento entre BEGIN y COMMIT.
- Colas de bloqueo: no estabas esperando al poseedor original sino al que espera el bloqueo exclusivo por delante de ti.
Cómo diagnosticarlo
-- Quién bloquea a quién, ahora mismo:
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));
Configura también log_lock_waits = on: las esperas más largas que deadlock_timeout se registran con ambos lados, dándote un historial de contención, no solo la vista en vivo.
Cómo solucionarlo
- Para migraciones, conserva el patrón y añade reintentos:
Timeout corto, bucle de reintento, ventana fuera de horas pico: el estándar para un DDL sin dramas.SET lock_timeout = '3s'; ALTER TABLE orders ADD COLUMN note text; -- si falla: reintenta con backoff - Ocúpate del bloqueador: arregla el trabajo que mantiene transacciones abiertas; configura
idle_in_transaction_session_timeout; como último recurso deliberado,pg_terminate_backend(pid). - Prefiere las variantes no bloqueantes donde existan:
CREATE INDEX CONCURRENTLY, backfills por lotes en vez de un único UPDATE gigante.
