The "Canceling statement due to statement timeout" error occurs when a query exceeds the configured statement_timeout duration. This safety mechanism prevents runaway queries from consuming excessive resources and helps maintain system responsiveness.
Impact
This error terminates long-running queries, which can cause application failures if timeouts are too aggressive. However, it protects the database from resource exhaustion caused by inefficient queries or missing indexes.
Common Causes
- Slow queries due to missing indexes
- Full table scans on large tables
- Complex joins without proper optimization
- Statement timeout configured too low
- Inefficient query logic
- Large data aggregations
- Lock waits counted toward timeout
- Database or network latency
Troubleshooting and Resolution Steps
Check current statement_timeout:
-- Check current value SHOW statement_timeout; -- Check default SELECT setting, unit FROM pg_settings WHERE name = 'statement_timeout';Adjust statement_timeout appropriately:
-- Set for current session SET statement_timeout = '30s'; SET statement_timeout = '5min'; SET statement_timeout = 0; -- Disable (no timeout) -- Set for specific transaction BEGIN; SET LOCAL statement_timeout = '60s'; -- Your long-running query COMMIT; -- Set for specific user ALTER USER reporting_user SET statement_timeout = '5min'; -- Set for specific database ALTER DATABASE mydb SET statement_timeout = '30s'; -- Set globally (requires reload) ALTER SYSTEM SET statement_timeout = '30s'; SELECT pg_reload_conf();Identify slow query causing timeout:
-- Enable slow query logging in postgresql.conf ALTER SYSTEM SET log_min_duration_statement = 1000; -- Log queries > 1s SELECT pg_reload_conf(); -- View currently running long queries SELECT pid, now() - query_start AS duration, state, query FROM pg_stat_activity WHERE state = 'active' AND now() - query_start > interval '5 seconds' ORDER BY duration DESC;Optimize the slow query with EXPLAIN:
-- Analyze query plan EXPLAIN ANALYZE SELECT * FROM orders o JOIN customers c ON o.customer_id = c.id WHERE o.created_at > '2024-01-01'; -- Look for: -- - Seq Scan (full table scan) on large tables -- - High cost estimates -- - Nested loops on large datasetsAdd missing indexes:
-- Create index on frequently filtered columns CREATE INDEX idx_orders_created_at ON orders(created_at); -- Composite index for multi-column filters CREATE INDEX idx_orders_customer_date ON orders(customer_id, created_at); -- Partial index for specific conditions CREATE INDEX idx_active_orders ON orders(created_at) WHERE status = 'active';Optimize query structure:
-- BAD: Inefficient subquery SELECT * FROM orders WHERE customer_id IN ( SELECT id FROM customers WHERE country = 'US' ); -- GOOD: Use JOIN SELECT o.* FROM orders o JOIN customers c ON o.customer_id = c.id WHERE c.country = 'US'; -- BAD: SELECT * SELECT * FROM large_table; -- GOOD: Select only needed columns SELECT id, name, email FROM large_table;Batch large operations:
-- BAD: Update all rows at once UPDATE large_table SET processed = true WHERE status = 'pending'; -- GOOD: Process in batches DO $$ DECLARE batch_size INTEGER := 1000; rows_updated INTEGER; BEGIN LOOP UPDATE large_table SET processed = true WHERE id IN ( SELECT id FROM large_table WHERE status = 'pending' AND NOT processed LIMIT batch_size ); GET DIAGNOSTICS rows_updated = ROW_COUNT; EXIT WHEN rows_updated = 0; COMMIT; PERFORM pg_sleep(0.1); -- Brief pause between batches END LOOP; END $$;Use pagination for large result sets:
-- BAD: Return all results SELECT * FROM orders ORDER BY created_at DESC; -- GOOD: Use pagination SELECT * FROM orders ORDER BY created_at DESC LIMIT 100 OFFSET 0; -- BETTER: Use keyset pagination for performance SELECT * FROM orders WHERE created_at < '2024-12-01' ORDER BY created_at DESC LIMIT 100;Handle timeout in application:
# Python with psycopg2 from psycopg2 import OperationalError def execute_with_timeout(query, params, timeout_seconds=30): try: conn = get_connection() cursor = conn.cursor() # Set timeout for this query cursor.execute(f"SET statement_timeout = '{timeout_seconds}s'") cursor.execute(query, params) result = cursor.fetchall() conn.commit() return result except OperationalError as e: conn.rollback() if 'statement timeout' in str(e) or 'canceling statement' in str(e): print(f"Query exceeded {timeout_seconds}s timeout") # Handle timeout - maybe try with reduced dataset return execute_with_reduced_scope(query, params) raise def execute_with_reduced_scope(query, params): # Retry with smaller date range or LIMIT passMonitor and tune performance:
-- Find slow queries from pg_stat_statements -- (requires pg_stat_statements extension) CREATE EXTENSION IF NOT EXISTS pg_stat_statements; SELECT query, calls, total_exec_time, mean_exec_time, max_exec_time FROM pg_stat_statements ORDER BY mean_exec_time DESC LIMIT 20; -- Reset statistics SELECT pg_stat_statements_reset();
Additional Information
- statement_timeout includes lock wait time
- Default is 0 (disabled) which can be dangerous
- Set different timeouts for different user types (web vs batch)
- Always set reasonable timeouts to prevent resource exhaustion
- Combine with connection pooling for better resource management
- Use query optimization as primary solution, timeouts as safety net
- pg_stat_statements extension helps identify problematic queries
Frequently Asked Questions
Q: What's a reasonable statement_timeout value?
A: For web applications: 10-30 seconds. For reporting/analytics: 5-15 minutes. For batch jobs: longer or disabled. Adjust based on your use case.
Q: Does statement_timeout cancel the transaction or just the statement?
A: It cancels only the current statement, not the entire transaction (unless in autocommit mode).
Q: Can I set different timeouts for different query types?
A: Yes, use SET LOCAL statement_timeout within transactions or set at user/database level.
Q: Why is my fast query still timing out?
A: Check if it's waiting for locks. statement_timeout includes lock wait time. Also check network latency and connection issues.
Q: Should I disable statement_timeout for batch jobs?
A: You can set it very high or to 0, but it's safer to set a reasonable upper limit (e.g., 1 hour) to catch infinite loops or runaway queries.
Q: How do I debug which part of a complex query is slow?
A: Use EXPLAIN (ANALYZE, BUFFERS) to see detailed timing for each operation. Break complex queries into CTEs to identify slow parts.
Q: What's the difference between statement_timeout and lock_timeout?
A: statement_timeout limits total query execution time. lock_timeout only limits time waiting for locks.
Q: Can application connection pooler affect timeouts?
A: Yes, poolers like PgBouncer have their own timeouts. Ensure pooler timeouts are longer than statement_timeout.