PostgreSQL Error 25P03: idle in transaction session timeout
PostgreSQL error code 25P03 is raised when a session has been sitting in the idle in transaction state — meaning a transaction was opened with BEGIN but no subsequent SQL was executed — for longer than the duration specified by idle_in_transaction_session_timeout. When this threshold is exceeded, PostgreSQL forcibly terminates the session to reclaim resources, release locks, and prevent table bloat caused by long-running open transactions. This is a deliberate safety mechanism, not a bug, and understanding its root causes is key to resolving it permanently.
Top 3 Causes
1. Application Code Holding Transactions Open During Non-DB Work
The most common cause: your application opens a transaction, then performs time-consuming work unrelated to the database (external API calls, user input, business logic processing) before committing.
-- BAD pattern: transaction left open during external work
BEGIN;
UPDATE orders SET status = 'processing' WHERE order_id = 12345;
-- Application now waits for a payment API response (could take 30+ seconds)
-- PostgreSQL session sits idle in transaction -> 25P03 triggered
COMMIT;
-- GOOD pattern: commit before external work
BEGIN;
UPDATE orders SET status = 'processing' WHERE order_id = 12345;
COMMIT;
-- Now call the external API outside the transaction scope
2. Connection Pool Returning Connections Without Committing
When using connection poolers like PgBouncer or HikariCP, a connection can be returned to the pool without a proper COMMIT or ROLLBACK. The pooler thinks the connection is healthy, but PostgreSQL sees it as idle in transaction.
-- Check for sessions currently idle in transaction
SELECT
pid,
usename,
application_name,
client_addr,
state,
now() - state_change AS idle_duration,
left(query, 80) AS last_query
FROM pg_stat_activity
WHERE state = 'idle in transaction'
ORDER BY idle_duration DESC;
-- Force terminate sessions idle for more than 2 minutes
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE state = 'idle in transaction'
AND state_change < now() - INTERVAL '2 minutes';
3. ORM / Driver Auto-Transaction Mismanagement
Frameworks like SQLAlchemy, Django ORM, or Hibernate often start transactions implicitly. When autocommit is disabled, even a plain SELECT opens a transaction that must be explicitly closed.
-- Diagnose: see how long transactions have been open
SELECT
pid,
usename,
now() - xact_start AS transaction_age,
state,
query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
AND state = 'idle in transaction'
ORDER BY transaction_age DESC;
Quick Fix Solutions
Set the timeout at the appropriate level to prevent runaway sessions:
-- Set globally via ALTER SYSTEM (value in milliseconds)
ALTER SYSTEM SET idle_in_transaction_session_timeout = '300000'; -- 5 minutes
SELECT pg_reload_conf();
-- Verify the setting
SHOW idle_in_transaction_session_timeout;
-- Apply per role (recommended for fine-grained control)
ALTER ROLE app_user SET idle_in_transaction_session_timeout = '120000'; -- 2 minutes
-- Apply per database
ALTER DATABASE myapp_db SET idle_in_transaction_session_timeout = '300000';
-- Pair with statement_timeout for defense in depth
SET idle_in_transaction_session_timeout = '60000'; -- 1 min
SET statement_timeout = '30000'; -- 30 sec
Prevention Tips
1. Monitor pg_stat_activity proactively.
Integrate the query below into your monitoring stack (Prometheus, Datadog, Zabbix) and alert when idle in transaction session count or duration exceeds your threshold:
SELECT
COUNT(*) AS idle_txn_count,
MAX(EXTRACT(EPOCH FROM (now() - state_change))) AS max_idle_secs
FROM pg_stat_activity
WHERE state = 'idle in transaction';
2. Configure your connection pool's lifetime settings shorter than idle_in_transaction_session_timeout.
Set idle_timeout and max_lifetime in PgBouncer or HikariCP to values lower than the PostgreSQL timeout so the pooler cleans up stale connections before PostgreSQL forcibly kills them. Always enable autocommit for workloads that don't require explicit transaction control, and keep transaction scopes as narrow as possible in your application code.
Related Errors
| Code | Name | Notes |
|---|---|---|
25P02 |
in_failed_sql_transaction |
Queries attempted after a failed transaction; related idle in transaction (aborted) state |
57014 |
query_canceled |
Triggered by statement_timeout; often handled alongside 25P03 |
57P01 |
admin_shutdown |
Session killed by admin; similar unexpected disconnection pattern |
08006 |
connection_failure |
Client-side error when a pooler reuses a connection killed by 25P03 |
📖 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)