← 中文资源

简体中文版本

诊断并解除 Postgres 锁等待链

查询 pg_locks 与 pg_stat_activity 找出阻塞会话与受害者队列,再决定终止、等待或收窄事务范围。

阅读英文原文

目标

锁队列看起来像一百个生病的请求,其实往往是一个看似健康的反派:一个握着锁却什么也不做的会话。pg_blocking_pids 的自连接把堆积的等待者变成一张“谁阻塞谁、阻塞了多久”的清单。链条头部一旦可见,等待、终止还是改代码的决策就一目了然,空闲事务超时则防止下一个被遗弃的会话重演事故。

步骤要点

  1. 用 pg_stat_activity 自连接 pg_blocking_pids 列出每对“等待者-阻塞者”、阻塞查询与持锁时长。
  2. 统计 active 会话数量,量化排队规模是否远超基线。
  3. 检查阻塞者状态:idle in transaction 长达 20 分钟的会话基本是被遗忘的事务,复核后可安全终止。
  4. 确认无误后执行 pg_terminate_backend(),等待者立即继续,API 连接压力回落。
  5. 给应用角色设 idle_in_transaction_session_timeout=60s、系统级 lock_timeout=10s,让被遗弃事务自动清场、锁等待响亮失败。

排错提示

pg_blocking_pids 为空但仍在等,可能是咨询锁或轻量锁,去查 granted=false 的 pg_locks 与等待者的 wait_event;同一会话反复成为阻塞者,说明该代码路径在事务内做慢速外部 I/O,应把外部调用移出事务;终止会回滚其工作,先确认事务确被遗弃,拿不准就给受害者配 lock_timeout 并对阻塞者运行时长告警。

适用边界

查看活动视图需要监控权限,应用角色通常没有;终止后端是应急手段而非修复,事务范围缺陷仍需代码变更。

代码示例

SELECT blocked.pid AS waiting_pid,
       blocking.pid AS blocking_pid,
       now() - blocking.query_start AS held_for
FROM pg_stat_activity blocked
JOIN pg_stat_activity blocking
  ON blocking.pid = ANY(pg_blocking_pids(blocked.pid));