Inspect blocking locks
pg_inspect_locksIdentify blocked sessions and their blockers to pinpoint lock contention causing hangs or stuck queries. Shows blocked and blocking PIDs, lock types, contested relations, and queries for cluster-wide diagnosis.
Instructions
Show current lock contention: which sessions are blocked and who is blocking them. Returns blocked PID, blocking PID, lock types, relation being contested, and the queries involved. Use this first when a tool call hangs or the app feels stuck - it's the fastest way to identify a long-held transaction holding a lock. Row shape: one row per (blocked_pid, blocking_pid) pair. A session waiting on multiple blockers appears on multiple rows -- group/deduplicate by blocked_pid if you want a per-blocked-session count. Scope: CLUSTER-WIDE, unlike pg_health active_queries, which is filtered to the database in DATABASE_URL. Lock waits cross databases (a shared catalog, a long transaction in a sibling database), so the blocker is not always somewhere this connection could see -- but it does mean a row here may name a pid in another database entirely. relation is only resolved for locks in the CURRENT database or on a cluster-shared catalog (pg_authid, pg_database, ...); it is NULL for a lock held in another database, because a pg_class OID is only meaningful within its own database. A NULL relation has TWO unrelated causes, and lock_type is what tells them apart. When the wait is ON a relation (lock_type relation / extend / page / tuple), NULL means the lock is held in another database and cannot be named from here. When the wait is on something else (transactionid / virtualxid / advisory), relation was only ever a best-effort hint -- an alphabetical guess among the blocker's held write-intent locks, not authoritative -- so NULL there means the guess found no candidate, and the blocker is very likely LOCAL. Do not read that second case as 'somewhere else': use blocking_pid and blocking_query, which are populated either way. Note also that pg_cancel_backend / pg_terminate_backend signal by PID across the whole cluster, so even a genuinely cross-database blocker is actionable via pg_kill. Use the blocked/blocking query text to disambiguate which table is actually contested.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max blocked/blocker pairs (default 50). |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| rows | Yes |