Find Idle in Transaction Sessions in PostgreSQL
How to Find Idle in Transaction Sessions in PostgreSQL
An idle-in-transaction session is a database connection that has started a transaction but is not currently running a query. It is just sitting there, holding the transaction open. This is different from a regular idle connection, which has no open transaction.
Idle-in-transaction sessions are a common problem. They hold locks, block autovacuum, and prevent rows from being cleaned up. If they stay open long enough, they can cause serious performance problems β including transaction ID wraparound.
These queries find all sessions in this state and flag the ones that have been open for too long.
Sample Code
Find all idle-in-transaction sessions:
1SELECT *
2FROM pg_stat_activity
3WHERE state = 'idle in transaction'
4 AND xact_start IS NOT NULL;
Find sessions idle in transaction for more than 5 minutes:
1SELECT
2 pid,
3 usename,
4 state,
5 query,
6 age(clock_timestamp(), query_start) AS idle_duration,
7 client_addr
8FROM
9 pg_stat_activity
10WHERE
11 state = 'idle in transaction'
12 AND age(clock_timestamp(), query_start) > interval '5 minutes';
Notes: Works on all supported PostgreSQL versions. Adjust the interval '5 minutes' threshold to match what is acceptable for your workload. The first query gives a full picture; the second filters to sessions that have been idle long enough to be a problem.
Code Breakdown
pg_stat_activityβ A built-in view that shows all current database sessions and their state.state = 'idle in transaction'β Filters to sessions that are inside an open transaction but not actively running a query. This is the state that causes problems.xact_start IS NOT NULLβ Confirms the session has an active transaction. This field is set when the transaction started and cleared when it ends.age(clock_timestamp(), query_start)β Calculates how long the session has been in this state.clock_timestamp()returns the real current time, not the transaction time.pidβ The process ID of the session. Use this to cancel or terminate it.usenameβ The database user running the session.queryβ The last query that ran in this session. This can help you identify where in your application the transaction was opened.client_addrβ The IP address of the client that opened this connection. Useful for identifying the source application or server.interval '5 minutes'β The time threshold. Sessions older than this are returned. Adjust based on your application's normal transaction duration.
Key Points
- Any session with
state = 'idle in transaction'has an open transaction. It is holding all the locks that transaction acquired. - The longer a transaction stays open, the more it blocks autovacuum and potentially blocks other queries waiting for the same rows.
- A session that shows the same
queryover and over but is alwaysidle in transactionis likely a bug in your application β a transaction was opened but never committed or rolled back. client_addrhelps you trace the problem back to the application or service that created the connection.
Insights and Explanations
To understand what locks an idle-in-transaction session is holding, join to pg_locks:
1SELECT
2 a.pid,
3 a.usename,
4 a.state,
5 age(clock_timestamp(), a.xact_start) AS duration,
6 l.relation::regclass AS locked_table,
7 l.mode,
8 l.granted
9FROM pg_stat_activity a
10JOIN pg_locks l ON l.pid = a.pid
11WHERE a.state = 'idle in transaction'
12ORDER BY duration DESC;
To cancel the query in a session (the session stays connected but the transaction is rolled back):
1SELECT pg_cancel_backend(pid);
To terminate the session entirely (disconnects the client):
1SELECT pg_terminate_backend(pid);
Use pg_cancel_backend first β it is less disruptive. If the session does not respond, use pg_terminate_backend.
To prevent idle-in-transaction sessions from piling up, set a timeout at the database or role level:
1-- Set a 10-minute limit for all new connections
2ALTER DATABASE your_database SET idle_in_transaction_session_timeout = '10min';
3
4-- Or for a specific role
5ALTER ROLE your_role SET idle_in_transaction_session_timeout = '10min';
When the timeout is reached, PostgreSQL automatically terminates the idle transaction and rolls it back.
Additional Considerations
- Permissions: Superusers can see all sessions in
pg_stat_activity. Regular users can only see their own sessions unless granted thepg_monitorrole. - Application fix: The root cause of idle-in-transaction sessions is usually application code that opens a transaction and then waits (for user input, an API call, etc.) before committing. Fix the application rather than relying on timeouts alone.
- Connection poolers: PgBouncer and similar tools can hold connections open in this state if not configured correctly. Check your pooler's
server_idle_timeoutandpool_modesettings.
References
Posts in this series
- Identify Blocking PostgreSQL Queries with pg_stat_activity
- PostgreSQL query to find columns containing only NULL values
- Analyze PostgreSQL Cache-Hit Ratio with pg_stat_statements
- How to Monitor Slow Running Queries in PostgreSQL
- Find Idle in Transaction Sessions in PostgreSQL
- Monitor PostgreSQL Index Build Progress with SQL
- Find PostgreSQL Tables That Need VACUUM FREEZE
- Detect PostgreSQL Transaction ID Wraparound Risk