pg_locks需关联pg_stat_activity查锁等待关系:granted=false为等待者,granted=true为持有者;用locktype过滤并解析relation和transactionid;pg_blocking_pids()不递归,需CTE查阻塞链;query为空时关注state和xact_start判断空闲事务。

pg_locks 里怎么看谁在等锁、谁在持锁
pg_locks 是实时锁视图,但默认不带会话信息,直接查容易看懵。关键是要关联 pg_stat_activity 把 PID、查询语句、状态都补全。
常用组合查询逻辑:先找 granted = false 的等待行(被阻塞者),再通过 locktype、database、relation、transactionid 等字段反向匹配 granted = true 的持有行。
- 必须加
WHERE locktype IN ('relation', 'transactionid', 'tuple')过滤,避免被virtualxid或object类锁干扰 -
relation字段是 OID,用(SELECT relname FROM pg_class WHERE oid = l.relation)才能看清表名 - 对事务级锁(
locktype = 'transactionid'),transactionid字段对应的是pg_stat_activity.backend_xid,不是pid
pg_blocking_pids() 怎么用才不漏掉嵌套阻塞
pg_blocking_pids(pid) 只返回**直接阻塞**该 PID 的会话,不递归。如果 A → B → C(C 阻塞 B,B 阻塞 A),查 A 只得到 B,查不到 C。
要定位完整阻塞链,得写递归 CTE 或多层嵌套调用:
WITH RECURSIVE blocked AS (
SELECT pid, pg_blocking_pids(pid) AS blockers
FROM pg_stat_activity
WHERE state = 'active' AND pg_blocking_pids(pid) != '{}'
UNION ALL
SELECT b.pid, pg_blocking_pids(b.pid)
FROM blocked b, LATERAL (SELECT unnest(blockers)) AS x(blocker_pid)
WHERE b.pid = x.blocker_pid
)
实际排查时更推荐用现成脚本或 pg_wait_sampling 扩展,它能捕获跨层级的 wait event。
为什么查到持锁会话却看不到它的 SQL?
常见原因是该会话处于 idle in transaction 状态,pg_stat_activity.query 已为空(只保留初始语句,后续没新查询就清空了),但锁还在。
- 优先看
pg_stat_activity.state和backend_start/xact_start时间差,判断是否长时间空闲事务 -
pg_stat_activity.wait_event_type为Lock表示正在等锁;为Client或空则大概率是持锁方且没在执行新语句 - 若
query字段为空,用pg_backend_pid()对比pid并结合日志(log_min_duration_statement)回溯原始操作
阻塞刚发生时,如何秒级抓现场?
依赖单次查询容易错过瞬时阻塞。真正有效的做法是「主动轮询 + 条件触发」:
- 用
pg_stat_activity和pg_locks联查,每 2 秒跑一次,条件设为wait_event = 'Lock' AND state = 'active' - 配合
pg_terminate_backend(pid)写自动熔断逻辑(比如等待超 30 秒就 kill) - 生产环境建议提前开启
log_lock_waits = on,这样只要锁等待超deadlock_timeout(默认 1s),就会记日志,无需手动轮询
复杂点在于事务嵌套和 prepared statement 场景下,pg_blocking_pids() 返回的 PID 可能已退出,而锁仍由 backend_xid 持有——这时候只能靠 pg_locks.transactionid 关联 pg_stat_activity.backend_xid,而不是简单 join pid。










