Statement Timeout Cancels Long Query
A statement that ran fine before now fails with SQLSTATE 57014 and the message 'canceling statement due to statement timeout'. It typically strikes...
In 10 seconds
A reporting query that used to finish suddenly started dying with 'ERROR: canceling statement due to statement timeout'. The platform team had set a statement_timeout guardrail so no single query can monopolize a connection, and this query now runs past the budget. The temptation is to disable the timeout for everyone -- which removes the protection that keeps one runaway query from starving the database. The right move is narrower: lift the limit for the one vetted long operation with SET LOCAL inside a transaction, or make the query fast enough to fit the budget, while leaving the global guardrail in place.
Why This Happens
statement_timeout sets a maximum wall-clock time for any single statement. When the timer elapses the backend interrupts itself and cancels the statement with 57014 -- by design, to stop one query from holding a connection (and its locks and resources) indefinitely. Here the guardrail is set at the database level, so every new session inherits it; the long operation simply runs past the budget and gets cancelled. Nothing is wrong with the timeout -- it is protecting the server -- which is why the fix is to scope an exception narrowly rather than remove the protection.
Diagnose
Free Tier -- Basic Approach
Read the budget the statement exceeded. One value tells you the limit you are up against.
SHOW statement_timeout;
statement_timeout ------------------- 500ms (1 row)
SHOW statement_timeout returns 500ms -- the wall-clock limit the long operation blew past. This is the number you compare your query's runtime against before deciding whether to speed up the query or grant a scoped exception.
Fix
For a genuinely long, vetted operation, lift the limit for just that statement. SET LOCAL statement_timeout = 0 inside a transaction exempts the operation and reverts at COMMIT, so the global guardrail is never touched.
BEGIN; SET LOCAL statement_timeout = 0; SELECT pg_sleep(1), 'completed' AS status; COMMIT;
BEGIN
SET
pg_sleep | status
----------+-----------
| completed
(1 row)
COMMIT
BEGIN; SET LOCAL statement_timeout = 0; <long operation>; COMMIT. Here SELECT pg_sleep(1), 'completed' returns the row with status = completed instead of being cancelled -- the operation finished because the budget was lifted for this transaction only.
Verify
Confirm the exception was scoped, not global: a fresh session is back under the 500ms guardrail, and an un-overridden long operation is cancelled again -- proving the fix exempted one statement rather than disabling the protection.
SHOW statement_timeout;
statement_timeout ------------------- 500ms (1 row)
SELECT pg_sleep(2);
ERROR: 57014: canceling statement due to statement timeout LOCATION: ProcessInterrupts, postgres.c:3394
SHOW statement_timeout in a new session still returns 500ms (the SET LOCAL override was transaction-scoped and is gone), and SELECT pg_sleep(2) is once more cancelled with 'canceling statement due to statement timeout'. The guardrail is intact for everyone who did not explicitly opt out.
⏪ Rollback
Nothing to roll back from the cancellation itself: a statement cancelled by statement_timeout is aborted, so any changes it was making are discarded automatically (the transaction does not commit). The fix uses SET LOCAL statement_timeout = 0, which is transaction-scoped and reverts at COMMIT or ROLLBACK -- it leaves no lasting configuration change. If you had instead changed the database default with ALTER DATABASE ... SET statement_timeout, you reverse it with ALTER DATABASE ... RESET statement_timeout.
📦 Version Matrix (PG 14--18)
Run the over-budget operation and the SET LOCAL-scoped fix on PostgreSQL 14 through 18. Every version cancels with the same message and completes the same way under the exception.
| Version | Behavior | Version Notes |
|---|---|---|
PG 14.23 |
broken=canceling statement due to statement timeout | ✗ Not available -- use stats_reset age |
PG 15.18 |
broken=canceling statement due to statement timeout | ✗ Not available -- use stats_reset age |
PG 16.14 |
broken=canceling statement due to statement timeout | ✗ Not available -- use stats_reset age |
PG 17.10 |
broken=canceling statement due to statement timeout | ✗ Not available -- use stats_reset age |
PG 18.4 |
broken=canceling statement due to statement timeout | ✗ Not available -- use stats_reset age |
⚠ Common Mistakes
Stop this incident before it pages you
The free runbook clears today's failure. Pro adds the standing guardrails -- the alert query, the dashboard, and the managed-service knobs -- so it never becomes an incident again.
- ✓ A ready-to-paste alert query with the right thresholds.
- ✓ The topology change that prevents recurrence.
- ✓ The exact knobs on RDS, Azure Flexible Server & Cloud SQL.
Frequently Asked Questions
Should I just raise or disable statement_timeout to make this go away?
Not globally. The timeout protects the server from a single query monopolizing a connection and its locks. For a genuinely long, vetted operation, lift the limit for that one statement with SET LOCAL statement_timeout = 0 inside a transaction; for a query that should not be slow, fix the query (index, better plan) so it fits the existing budget.
Why does SET LOCAL need a transaction?
SET LOCAL changes a setting only until the end of the current transaction. In autocommit mode each statement is its own transaction, so SET LOCAL would expire immediately and not cover the next statement. Wrapping the long operation in BEGIN; SET LOCAL statement_timeout = 0; <operation>; COMMIT keeps the exception in force for exactly that operation and reverts automatically.
How do I confirm it was statement_timeout and not a different timeout?
Compare the three session timeouts with SELECT name, setting, unit FROM pg_settings WHERE name IN ('statement_timeout','lock_timeout','idle_in_transaction_session_timeout'). The two non-firing ones read 0 (disabled), and the error message 'due to statement timeout' names statement_timeout explicitly. lock_timeout and idle_in_transaction_session_timeout produce different message text.
Where should the guardrail live?
Set statement_timeout at the level that matches the risk: per-role for an analytics login (ALTER ROLE), per-database for an OLTP database (ALTER DATABASE), or per-session for ad-hoc work. Keep it non-zero as a standing protection, and grant exceptions narrowly with SET LOCAL rather than turning it off for everyone.