| title | PostgreSQL Canceling Statement Due to Statement Timeout | |||||
|---|---|---|---|---|---|---|
| slug | postgresql-canceling-statement-due-to-statement-timeout | |||||
| technologies |
|
|||||
| severity | medium | |||||
| tags |
|
|||||
| related |
|
|||||
| last_reviewed | 2026-06-27 |
ERROR: canceling statement due to statement timeout
ERROR: canceling statement due to statement timeout
STATEMENT: SELECT * FROM events e JOIN sessions s ON s.id = e.session_id
WHERE e.created_at > now() - interval '90 days';
statement_timeout is a per-session limit (in milliseconds) on how long a single
SQL statement may run. When a statement exceeds it, PostgreSQL cancels the query
and raises ERROR: canceling statement due to statement timeout, rolling back
that statement. It is a deliberate guardrail, not a database fault β it protects
the server from runaway queries that would otherwise hold locks and consume
resources. The error tells you a query was too slow for its budget; the cause
is either a genuinely slow query or a timeout set too aggressively for the work.
- postgresql (query executor,
statement_timeout)
medium β the individual query fails and must be retried or optimized; the server stays healthy (this is the timeout working as intended). High if it breaks a critical query path on every execution.
- A missing or unusable index forces a sequential scan that exceeds the timeout β the most common cause.
- The query genuinely processes too much data (unbounded range, large join) for the allotted time.
- The statement waited on a lock held by another transaction and the wait pushed it past the limit.
statement_timeoutis set too low (globally or via a pooler) for legitimate reporting/batch queries.- Stale planner statistics produce a bad plan that is far slower than necessary.
PostgreSQL arms a timer when a statement begins executing; if the timer fires
before the statement completes, the backend sends itself a cancel and the
executor aborts at the next interrupt check. The timeout is the messenger β the
real question is whether the statement is slow because of a poor plan
(missing/unused index, bad statistics), excessive data volume, or lock waiting.
Note that lock-wait time counts toward statement_timeout, so a query blocked
behind a long transaction can time out even though its own execution would be
fast. EXPLAIN (and EXPLAIN ANALYZE on a safe copy) distinguishes a slow plan
from a blocking problem.
# Current timeout values (session/global and any per-role override)
psql -c "SHOW statement_timeout;"
psql -c "SELECT rolname, rolconfig FROM pg_roles WHERE rolconfig IS NOT NULL;"
# Plan WITHOUT running it β spot seq scans / missing-index estimates
psql -c "EXPLAIN SELECT * FROM events WHERE created_at > now() - interval '90 days';"
# Is the slow statement actually waiting on a lock?
psql -c "SELECT pid, wait_event_type, wait_event, now()-query_start AS runtime, query
FROM pg_stat_activity WHERE state='active' ORDER BY runtime DESC LIMIT 10;"
# Index usage / unused indexes that hint at scan problems
psql -c "SELECT relname, seq_scan, idx_scan FROM pg_stat_user_tables ORDER BY seq_scan DESC LIMIT 10;"# EXPLAIN revealing the problem:
Seq Scan on events (cost=0.00..184213.00 rows=120 width=72)
Filter: (created_at > (now() - '90 days'::interval))
# vs. healthy after indexing:
Index Scan using events_created_at_idx on events (cost=0.43..812.10 rows=120 ...)
A Seq Scan with a huge cost on a large table, or a high wait_event_type = Lock, points to the underlying cause.
-
If
EXPLAINshows a sequential scan on a filtered/joined column, add an index (build concurrently in production to avoid locking the table):CREATE INDEX CONCURRENTLY events_created_at_idx ON events (created_at);
-
Refresh stale statistics so the planner chooses a better plan:
ANALYZE events;
-
If the timeout is too low for legitimate heavy queries, raise it for that work only β do not weaken the global guardrail:
SET statement_timeout = '120s'; -- session-scoped, e.g. for a report job
-
If lock waiting is the cause, resolve the blocking transaction (see the deadlock/locking diagnostics) rather than just extending the timeout.
psql -c "EXPLAIN ANALYZE SELECT * FROM events WHERE created_at > now() - interval '90 days';"
# Expect an Index Scan and an actual time well under statement_timeout; query completes.- Keep
statement_timeoutset as a safety net (e.g. a few seconds for OLTP) and grant longer budgets only to report/batch roles. - Index the columns your hot queries filter and join on; review
pg_stat_user_tables. - Keep autovacuum/auto-analyze healthy so plans stay accurate.
- Add query-latency monitoring so slow plans are caught before they time out.
postgresql Β· performance Β· timeout Β· queries Β· production