← 中文资源

简体中文版本

PostgreSQL — 检测到死锁

通过服务器日志定位冲突的加锁顺序,缩短事务,并为可重试的死锁增加有界重试。

阅读英文原文

死锁是加锁顺序缺陷

PostgreSQL 不阻止死锁,而是检测循环并中止其中一个事务。错误是症状:两个事务以相反顺序获取锁,或持锁时间过长形成循环。服务器日志包含涉及的语句和进程,应从这里开始,而不是从应用堆栈猜起。

ERROR: deadlock detected
DETAIL: Process 120 waits for ShareLock on transaction 121;
        blocked by process 121.

让顺序确定

如果转账更新账户 1 和 2,另一笔更新 2 和 1,按 id 排序后再加锁可以消除循环:

BEGIN;
SELECT id, balance FROM accounts
 WHERE id IN (1, 2)
 ORDER BY id
 FOR UPDATE;
UPDATE accounts SET balance = balance - 10 WHERE id = 1;
UPDATE accounts SET balance = balance + 10 WHERE id = 2;
COMMIT;

ORDER BY id ... FOR UPDATE 以稳定顺序获取行锁。批处理任务按主键顺序处理行,并小批量提交。不要在事务打开期间调用支付服务或等待人工,否则毫秒级锁会变成数秒的冲突窗口。

有界重试

死锁是暂时的,有界重试是合适的。捕获 SQLSTATE 40P01,回滚,带抖动等待后重试整个事务。限制尝试次数,失败时暴露错误而不是无限循环。确保事务没有会在重试时重复的外部副作用;如果有,移到 outbox 或使其幂等。

关注趋势

跟踪每分钟死锁数和涉及的主要语句。突然增加通常跟随新的查询计划、缺失索引或某条代码路径改变了加锁顺序。pg_stat_database.deadlocks 提供计数,服务器日志提供细节。修顺序,不要只修超时。