全部排错手册
数据库 / PostgreSQL更新于 4 分钟阅读 · 执行耗时因环境而异

PostgreSQL deadlock detected:用锁等待图修复并发顺序

死锁是事务互相等待形成的环;数据库中止一个事务只是保护机制,真正的修复通常在应用的访问顺序。

维护者 Kevin · Ovalk

适用范围与前提

适用于 PostgreSQL 事务及行/表锁;诊断需相应监控权限,托管数据库可能限制访问。

收集死锁错误和事务边界,注意查询及日志可能含客户数据。初步执行计划优先使用不带 ANALYZE 的 EXPLAIN;EXPLAIN ANALYZE 会真正执行语句。

示例不是可以直接粘贴的指令。请替换示例域名、路径、服务名和大写占位符。先收集证据;重载、回滚、清理及执行任务会改变系统状态,需批准影响范围与恢复计划。不要分享凭据或未脱敏日志。

常见症状

  • 应用报 deadlock detected
  • 同一业务操作偶发失败并可重试
  • 高并发写入时 p95 延迟上升

1. 收集完整死锁上下文

PostgreSQL 日志中的 deadlock detail 会给出等待关系和 SQL。把日志与请求 trace、事务边界和表访问顺序关联起来,单看一条 SQL 往往不足以复现。

SHOW log_lock_waits;
SHOW deadlock_timeout;
SELECT pid, wait_event_type, wait_event, state, query FROM pg_stat_activity WHERE state <> 'idle';

2. 找到不一致的锁获取顺序

典型模式是事务 A 先锁订单再锁库存,事务 B 先锁库存再锁订单。统一资源排序规则、缩短事务范围、避免把外部调用放进事务,通常比提高超时更有效。

SELECT locktype, relation::regclass, mode, granted, pid FROM pg_locks WHERE NOT granted OR relation IS NOT NULL;

3. 设计可观测的重试

死锁错误可对幂等事务做有限次数的指数退避重试,但重试不是无限吞掉错误。记录重试次数、业务键和慢事务,防止错误模式被隐藏。

SELECT datname, deadlocks, stats_reset FROM pg_stat_database WHERE datname = current_database();

解读证据

观察结果下一步检查
SQLSTATE 40P01表示死锁,受害事务已被中止;应按有限重试策略重试整个事务,而非只重试最后一条语句。
有锁等待、无死锁错误存在阻塞不一定形成循环;终止会话前检查 pg_blocking_pids 和事务持续时间。
报错后看不到循环PostgreSQL 可能已经打破循环;应使用当时记录的死锁详情,而不是要求后续快照重现。

说明性诊断案例

以下假设案例用于解释推理,并非客户真实事故,也不代表已在你的技术环境中测试。

事务 A 先更新库存行 10 再更新 20,事务 B 顺序相反,双方可能持有对方所需的行锁。获取锁前统一标识符顺序可以处理这种循环,延长超时不能解决。在测试库复现时应包含事务所有语句,而不只是报错查询。

验证恢复

  • 在测试环境并发运行受影响操作,确认数据库约束正确,且不再出现同样锁循环。
  • 在代表性负载下对比死锁数、重试次数及延迟,避免重试隐藏错误却恶化时延。

回滚与停止条件

将锁顺序或重试变更放入可回退的应用发布;代码回退无法撤销重复外部副作用,应确认幂等性,并避免发布期间混用新旧锁顺序。

预防与长期修复

  • 文档化跨表更新的锁顺序。
  • 事务仅包裹必要数据库操作,避免用户交互和网络调用。
  • 对锁等待、事务时长和死锁次数建立按服务维度的告警。

参考资料与纠错

请使用与你安装版本匹配的文档。以下资料解释基础行为,命令仍需结合实际环境验证。

向 Kevin 提交更正,请附页面地址、版本与脱敏复现步骤。请查看编辑原则(英文)