Cookbook recipe

Old open transaction holding locks and blocking VACUUM

Applies to PostgreSQL 13–17 Last reviewed Nov 2025 Grounded in source
Estimated investigation4 min

Scenario

A transaction has been open for minutes or hours without issuing new queries — it holds row locks, pins the transaction ID horizon, and prevents autovacuum from cleaning dead rows that other transactions can see. Diagnose…

Investigation Path

A transaction has been open for minutes or hours without issuing new queries — it holds row locks, pins the transaction ID horizon, and prevents autovacuum from cleaning dead rows that other transactions can see.

Diagnose it

-- Backends with old open transactions (regardless of current state):
SELECT pid,
       usename,
       application_name,
       state,
       backend_xid,
       backend_xmin,
       now() - xact_start        AS txn_age,
       now() - state_change      AS in_current_state,
       LEFT(query, 120)          AS last_query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY txn_age DESC NULLS LAST;

-- Locks held by the oldest transaction:
SELECT locktype, relation::regclass, mode, granted
FROM pg_locks
WHERE pid = <pid_of_old_txn>;

Why it happens

An open transaction pins backend_xmin — the oldest transaction ID that any
live backend might still need to read. VACUUM cannot remove dead tuple versions newer than
this horizon. On a busy database, even a transaction idle for a few minutes can accumulate
a large VACUUM debt, leading to table bloat. Logical replication also stalls if the publisher
has old backend_xmin entries.

How to fix it

-- Find the transaction pinning the oldest xmin:
SELECT pid, usename, application_name, state,
       age(backend_xmin)  AS xmin_age,
       now() - xact_start AS txn_age
FROM pg_stat_activity
WHERE backend_xmin IS NOT NULL
ORDER BY age(backend_xmin) DESC NULLS LAST;

-- Terminate it (after confirming with the application team):
SELECT pg_terminate_backend(<pid>);

Prevent it next time

Set idle_in_transaction_session_timeout to automatically close sessions that
go idle inside a transaction. Monitor max(age(backend_xmin)) from
pg_stat_activity as an alert metric — anything above a few thousand transactions
in OLTP, or above the autovacuum freeze threshold in long-running analytics, warrants
investigation (guidance).

This is a Pro lesson

Get every Learning Pathway and cookbook recipe — grounded in PostgreSQL source code, with diagnostics, fixes, and prevention for each topic.

Continue this lesson to learn:

  • Related & next steps
  • All 36 Learning Pathway lessons
  • 170+ cookbook recipes
  • Source-grounded diagnostics & fixes

Secure checkout Cancel anytime Source-grounded

Career Impact

This scenario builds production judgment and operational confidence under pressure.

Open Career Dashboard →

Keep going

Related & next steps

Was this helpful?

← All cookbook recipes