PostgreSQL error: canceling statement due to statement timeout
ERROR: canceling statement due to statement timeout
The statement ran longer than statement_timeout allows, measured in wall-clock time from when the server received it — including any time spent waiting on locks. PostgreSQL cancelled it and rolled back its effects.
What this error means
SQLSTATE 57014 (query_canceled) covers all statement cancellations; the message tells you which one you got:
due to statement timeout— this page:statement_timeoutexpired.due to user request— someone hit Ctrl+C or calledpg_cancel_backend().due to lock timeout— a different setting (lock_timeout) and a different SQLSTATE (55P03): the statement gave up waiting for a lock.
The subtlety that matters: statement_timeout counts lock waits too. A query that normally takes 50 ms can hit a 30 s timeout because it sat behind someone's ALTER TABLE. Before "optimizing" the query, find out which case you're in.
Common causes
- A genuinely slow query: missing index, a plan that flipped after data growth or stale statistics, an unbounded scan.
- Blocked on a lock: the query was fine but waited behind DDL or a long write transaction.
- The timeout is set lower than you think — globally in
postgresql.conf, per role or database (ALTER ROLE ... SET statement_timeout), per session by the driver/ORM, or per transaction. These override each other and it's easy to lose track. - A batch job or migration that legitimately needs minutes, running under an OLTP-sized timeout.
How to diagnose it
-- Da dove viene il valore attivo:
SELECT name, setting, source
FROM pg_settings
WHERE name = 'statement_timeout';
-- Override per ruolo/database:
SELECT * FROM pg_db_role_setting;
The cancelled statement is written to the server log together with the error, so you know exactly which query it was. Then split the two cases:
- Run it with
EXPLAIN (ANALYZE, BUFFERS)in a safe context and look at where the time goes — paste the JSON into the EXPLAIN visualizer and check the slowest nodes and row-estimate warnings. - If it reproduces live, check
wait_event_type/wait_eventinpg_stat_activitywhile it runs:Lockmeans it's a blocking problem, not a query-speed problem.
How to fix it
- Slow query → fix the query or the indexing; that's the real solution, the timeout only surfaced it.
- Legitimate long operation → scope the exception instead of raising the global value:
BEGIN; SET LOCAL statement_timeout = '30min'; -- vale solo per questa transazione -- ... migrazione o report ... COMMIT; - Lock waits → use
lock_timeoutfor DDL so it fails fast instead of queueing (and blocking everyone behind it), and hunt the long transaction that held the lock. - Keep a sane global cap. A modest global
statement_timeoutis protection against runaway queries; the answer to a false positive is a scoped override, not turning it off.
🔍
Related tool: the EXPLAIN ANALYZE visualizer — paste the plan of the query that timed out and see which node eats the time.
🔍
Related reading: How to read EXPLAIN ANALYZE.
