Skip to content

Lock waits

Heavyweight locks whose owner and blocking chain can usually be identified from SQL.

Lock is the most directly actionable class: a backend asked the lock manager for a heavyweight lock and an incompatible holder has not released it. Unlike LWLocks, these waits usually have rows in pg_locks and a useful result from pg_blocking_pids().

Read the blocking graph, not the victim count

Twenty waiters can all be symptoms of one idle transaction. Start at the head of the graph, then decide whether the holder is doing useful work, abandoned, or participating in an expected DDL/deploy window.

  • Normal: sub-second lock handoff during ordinary writes or planned DDL.
  • Watch: any user-facing waiter persists beyond its latency objective, or at least 10% of active sessions wait on the same root blocker.
  • Urgent: the root holder is idle in transaction, the chain keeps growing, critical DDL blocks traffic, or deadlocks begin to rise.

Events to recognize

Event Usually means
relation Table/index-level lock conflict, often DDL versus DML
transactionid Waiting for another transaction’s outcome, commonly row updates
tuple Competing tuple locks or a long row-lock queue
extend Sessions serialize while extending one relation
virtualxid DDL waits for transactions that might still use an object
advisory Application-defined advisory-lock coordination

Blocker query

SELECT w.pid AS waiting_pid, b.pid AS blocking_pid,
       w.wait_event, now() - w.query_start AS waiting_for,
       now() - b.xact_start AS blocker_xact_age,
       b.state AS blocker_state,
       left(w.query, 100) AS waiting_query,
       left(b.query, 100) AS blocking_query
FROM pg_stat_activity AS w
CROSS JOIN LATERAL unnest(pg_blocking_pids(w.pid)) AS x(pid)
JOIN pg_stat_activity AS b ON b.pid = x.pid
ORDER BY waiting_for DESC;

Common misreads

  • A session listed as a blocker is not automatically safe to terminate; it may be the only writer preserving an invariant.
  • transactionid does not mean transaction-ID exhaustion.
  • relation does not identify the relation by itself; join pg_locks.relation to pg_class.
  • Increasing lock_timeout changes how long victims wait; it does not remove the blocker.

Waiting to acquire an advisory user lock

Waiting to acquire a lock on a remote transaction being applied by a logical replication subscriber

Waiting to extend a relation

Waiting to update pg_database.datfrozenxid and pg_database.datminmxid

Waiting to acquire a lock on a non-relation database object

Waiting to acquire a lock on a page of a relation

Waiting to acquire a lock on a relation

Waiting to acquire a speculative insertion lock

Waiting to acquire a lock on a tuple

Waiting to acquire a user lock

Waiting to acquire a virtual transaction ID lock