PostgreSQL Field Guide

PostgreSQL 锁等待与死锁排查

识别阻塞链、避免死锁并正确处理 lock timeout 和 SQLSTATE 40P01

锁等待和死锁不同

  • 锁等待:会话等待另一个事务释放冲突锁;可能最终成功,也可能超时。
  • 死锁:形成等待环,任何参与者都无法自行前进;PostgreSQL 检测后中止其中一个事务。

死锁失败的 SQLSTATE 是 40P01。当前事务必须回滚;只有整个业务事务可安全重放时才做有界重试。

查看阻塞链

SELECT
  blocked.pid AS blocked_pid,
  blocker.pid AS blocker_pid,
  now() - blocked.query_start AS blocked_for,
  blocked.wait_event_type,
  blocked.wait_event,
  left(blocked.query, 120) AS blocked_query,
  left(blocker.query, 120) AS blocker_query
FROM pg_stat_activity AS blocked
CROSS JOIN LATERAL unnest(pg_blocking_pids(blocked.pid)) AS b(pid)
JOIN pg_stat_activity AS blocker ON blocker.pid = b.pid
ORDER BY blocked.query_start;

先检查 blocker 是否处于 idle in transaction、事务包含什么修改、应用是否仍存活。不要看到 PID 就立即终止。

减少死锁

  1. 所有代码路径按相同顺序锁定资源,例如总按 account id 升序。
  2. 事务只包含必须原子完成的数据库工作,不跨用户输入和外部 API。
  3. 为定位要更新行的条件建立合适索引,减少锁定/扫描范围。
  4. 对批量任务分块,并避免多个任务交叉处理相同键空间。
  5. 设置有业务依据的 lock_timeoutstatement_timeout
BEGIN;
SET LOCAL lock_timeout = '1s';
SET LOCAL statement_timeout = '10s';

SELECT id
FROM accounts
WHERE id = ANY($1::bigint[])
ORDER BY id
FOR UPDATE;

-- bounded writes
COMMIT;

终止会话前

pg_cancel_backend(pid) 请求取消当前语句;pg_terminate_backend(pid) 终止会话并回滚其事务。执行前确认:

  • PID 仍属于目标会话,避免使用陈旧截图;
  • 回滚可能耗时和产生额外 I/O;
  • 应用不会立即以相同方式重连并再次阻塞;
  • 被中止工作是否可重试、是否需要业务补偿。

完整锁模式与冲突矩阵见 PostgreSQL 18 显式锁定

Last updated on

On this page