PostgreSQL Error 25P03: idle in transaction session timeout
PostgreSQL error 25P03 is raised when a session has been sitting in an open transaction (idle in transaction state) without executing any SQL for longer than the configured idle_in_transaction_session_timeout threshold. When this happens, the PostgreSQL server forcibly terminates the backend process, rolling back any uncommitted work. This safeguard exists because long-running idle transactions hold locks and consume resources, potentially blocking other queries across the entire database.
Top 3 Causes
1. Missing COMMIT or ROLLBACK in Application Code
The most common cause is application code that opens a transaction but fails to close it — usually due to an unhandled exception, a slow external API call, or a logic bug that bypasses the commit path.
-- Check for sessions currently idle in transaction
SELECT
pid,
usename,
application_name,
state,
now() - state_change AS idle_duration,
query
FROM pg_stat_activity
WHERE state = 'idle in transaction'
ORDER BY idle_duration DESC;
-- Terminate sessions idle in transaction for more than 5 minutes
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle in transaction'
AND state_change < now() - INTERVAL '5 minutes'
AND pid <> pg_backend_pid();
2. Interactive Sessions Left Unattended
Developers or DBAs using psql, DBeaver, or DataGrip sometimes run BEGIN manually and then walk away or switch tasks, leaving the transaction open. This is especially dangerous in production if the session holds table-level locks.
-- Find idle in transaction sessions holding locks
SELECT
a.pid,
a.usename,
a.state,
now() - a.state_change AS idle_time,
l.relation::regclass AS locked_table,
l.mode
FROM pg_stat_activity a
JOIN pg_locks l ON a.pid = l.pid
WHERE a.state = 'idle in transaction'
AND l.granted = true
ORDER BY idle_time DESC;
3. Connection Pooler Misconfiguration
When using PgBouncer in session mode, clients may return a connection to the pool without properly closing the transaction, leaving the server-side connection stuck in idle in transaction state until the timeout fires.
-- Verify current timeout settings
SELECT name, setting, unit
FROM pg_settings
WHERE name IN (
'idle_in_transaction_session_timeout',
'statement_timeout',
'lock_timeout'
);
Quick Fix Solutions
Set the timeout parameter at the appropriate level to automatically clean up stale transactions:
-- Set globally (add to postgresql.conf, then reload)
ALTER SYSTEM SET idle_in_transaction_session_timeout = '5min';
SELECT pg_reload_conf();
-- Set per database
ALTER DATABASE mydb SET idle_in_transaction_session_timeout = '3min';
-- Set per role
ALTER ROLE app_user SET idle_in_transaction_session_timeout = '2min';
-- Set for current session only
SET idle_in_transaction_session_timeout = '1min';
-- Protect an individual transaction block
BEGIN;
SET LOCAL idle_in_transaction_session_timeout = '30s';
UPDATE accounts SET balance = balance - 100 WHERE id = 42;
COMMIT;
Prevention Tips
1. Always configure idle_in_transaction_session_timeout — never leave it at the default 0 (unlimited). For OLTP workloads, 2–5 minutes is a safe starting point. Treat this as a non-negotiable baseline in your postgresql.conf alongside statement_timeout and lock_timeout:
-- Recommended defensive timeout configuration
-- idle_in_transaction_session_timeout = '5min'
-- statement_timeout = '30min'
-- lock_timeout = '30s'
-- deadlock_timeout = '1s'
2. Enforce strict transaction boundaries in application code. Always use try/finally (or equivalent) patterns to guarantee COMMIT or ROLLBACK is called. If using a connection pool, prefer PgBouncer in transaction mode to prevent pooled connections from lingering in open transactions. Set up monitoring alerts (e.g., via Prometheus pg_stat_activity) to notify your team when the count of idle in transaction sessions exceeds a defined threshold.
Related Errors
| Error Code | Name | Relationship |
|---|---|---|
57014 |
query_canceled |
Fired by statement_timeout and lock_timeout; part of the same timeout family |
55P03 |
lock_not_available |
Sessions waiting on locks held by an idle-in-transaction session may hit this |
08006 |
connection_failure |
Client-side error after 25P03 forcibly closes the backend connection |
40P01 |
deadlock_detected |
Can occur alongside idle transactions that hold conflicting locks |
📖 Want a more detailed guide?
Check out the full in-depth version (Korean) on oraerror.com — includes detailed analysis, additional SQL examples, and prevention tips.
Top comments (0)