PostgreSQL cancels statements that exceed the lock_timeout waiting for locks on database objects. This error indicates lock contention—adjust the timeout, resolve blocking queries, or optimize transaction isolation to fix it.
The "Canceling statement due to lock timeout" error occurs when a PostgreSQL statement fails to acquire a required lock (on a table, row, index, or other object) within the time specified by the lock_timeout parameter. When enabled, lock_timeout aborts any statement that waits longer than the configured duration attempting to acquire a lock, returning this error to the client. This is a protective mechanism to prevent statements from blocking indefinitely on locked resources and creating application hangs or cascading failures.
Check if lock_timeout is set on your connection:
SHOW lock_timeout;If the output is 0 or 0ms, lock_timeout is disabled and this error shouldn't occur. If it's set to a specific value (e.g., 5s), that's your configured lock wait limit.
Find which queries are holding locks and blocking others:
SELECT
blocked_locks.pid AS blocked_pid,
blocked_activity.usename AS blocked_user,
blocked_activity.application_name AS blocked_app,
blocking_locks.pid AS blocking_pid,
blocking_activity.usename AS blocking_user,
blocking_activity.application_name AS blocking_app,
blocked_activity.query AS blocked_query,
blocking_activity.query AS blocking_query,
blocked_activity.state AS blocked_state,
blocking_activity.state AS blocking_state
FROM pg_locks blocked_locks
JOIN pg_stat_activity blocked_activity ON blocked_activity.pid = blocked_locks.pid
JOIN pg_locks blocking_locks ON blocking_locks.locktype = blocked_locks.locktype
AND blocking_locks.database IS NOT DISTINCT FROM blocked_locks.database
AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
AND blocking_locks.pid != blocked_locks.pid
JOIN pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid
WHERE NOT blocked_locks.granted
ORDER BY blocked_activity.query_start;This shows you exactly which transaction is blocking which, what they're doing, and how long they've been running.
If blocking transactions are long-running and no longer needed, terminate them:
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE pid != pg_backend_pid()
AND query_start < NOW() - INTERVAL '10 minutes'
AND state != 'idle';Be cautious: terminating a transaction in the middle of a modification can leave data in an inconsistent state if not properly handled. Only terminate queries you own or have explicit permission to terminate.
If your lock_timeout is too aggressive for legitimate blocking scenarios, increase it at the session level:
SET lock_timeout = '30s';Or set it for a specific application user/role:
ALTER ROLE myapp SET lock_timeout = '30s';Typical values are 5-30 seconds. Never set lock_timeout globally in postgresql.conf—always use session or role-level configuration so different workloads can have appropriate timeouts.
Restructure transactions to minimize lock duration and contention:
1. Use READ COMMITTED isolation (default):
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ COMMITTED;2. Lock rows only when needed:
-- Explicit locking for rows you will modify
SELECT * FROM users WHERE id = 42 FOR UPDATE;
UPDATE users SET balance = balance - 100 WHERE id = 42;
COMMIT;3. Minimize transaction duration:
- Move non-critical logic outside transactions
- Fetch data before entering a transaction
- Commit frequently in loops
4. Lock in consistent order across transactions:
If multiple transactions lock the same tables, always lock them in the same order to prevent deadlock chains.
Indexes reduce lock duration by enabling efficient queries:
-- If you're doing SELECT FOR UPDATE on frequently-queried columns
CREATE INDEX idx_users_status ON users(status) WHERE status = 'active';
-- If you're updating rows with WHERE conditions
CREATE INDEX idx_orders_customer_date ON orders(customer_id, created_at);Full table scans hold locks longer because they must scan every row. Targeted indexes allow the query to find and lock only the necessary rows.
The "Canceling statement due to lock timeout" error differs from "Lock timeout exceeded"—the latter is part of PostgreSQL's error code standard (ERRCODE_LOCK_NOT_AVAILABLE, SQLSTATE 55P03), while the former is the user-facing message when lock_timeout fires. Unlike deadlock_timeout (which detects circular lock dependencies, default 1s) and statement_timeout (which limits total query execution time), lock_timeout specifically limits time spent waiting for lock acquisition. If both lock_timeout and statement_timeout are configured, whichever fires first will cancel the statement. On heavily concurrent systems, monitor with SELECT COUNT(*) FROM pg_locks WHERE NOT granted to detect rising lock contention before timeouts occur. For DDL operations requiring ACCESS EXCLUSIVE locks, set a shorter lock_timeout (2-10s) to prevent a single blocked ALTER TABLE from queuing dozens of transactions and causing cascading timeouts. In high-throughput OLTP systems, consider using connection poolers (pgBouncer, PgPool) configured with TRANSACTION mode to reduce idle connections and overall lock contention.
ERROR: syntax error at end of input
Syntax error at end of input in PostgreSQL
Bind message supplies N parameters but prepared statement requires M
Bind message supplies N parameters but prepared statement requires M in PostgreSQL
Multidimensional arrays must have sub-arrays with matching dimensions
Multidimensional arrays must have sub-arrays with matching dimensions
ERROR: value too long for type character varying
Value too long for type character varying
insufficient columns in unique constraint for partition key
How to fix "insufficient columns in unique constraint for partition key" in PostgreSQL