PostgreSQL pg_locks / pg_blocking_pids 如何快速定位阻塞

15次阅读

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

PostgreSQL pg_locks / pg_blocking_pids 如何快速定位阻塞

pg_locks 里怎么看谁在等锁、谁在持锁

pg_locks 是实时锁视图,但默认不带会话信息,直接查容易看懵。关键是要关联 pg_stat_activity 把 PID、查询语句、状态都补全。

常用组合查询逻辑:先找 granted = false 的等待行(被阻塞者),再通过 locktypedatabaserelationtransactionid 等字段反向匹配 granted = true 的持有行。

  • 必须加 WHERE locktype IN ('relation', 'transactionid', 'tuple') 过滤,避免被 virtualxidobject 类锁干扰
  • 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.statebackend_start/xact_start 时间差,判断是否长时间空闲事务
  • pg_stat_activity.wait_event_typeLock 表示正在等锁;为 Client 或空则大概率是持锁方且没在执行新语句
  • query 字段为空,用 pg_backend_pid() 对比 pid 并结合日志(log_min_duration_statement)回溯原始操作

阻塞刚发生时,如何秒级抓现场?

依赖单次查询容易错过瞬时阻塞。真正有效的做法是「主动轮询 + 条件触发」:

  • pg_stat_activitypg_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。

text=ZqhQzanResources