数据库死锁时系统自动选回滚代价最小的事务作为牺牲者,依据等待图检测环路;SQL Server按优先级和开销、PostgreSQL选被阻塞最短者、MySQL按锁数量和事务大小判定;可通过设优先级、缩短事务、固定访问顺序等降低关键事务被牺牲概率;应用需捕获特定错误码并指数退避重试,配合日志分析与幂等设计实现健壮恢复。

当SQL数据库发生死锁时,系统会自动选择一个事务作为“牺牲者”进行回滚,以解除死锁。这个选择不是随机的,而是基于代价评估——通常选回滚成本最低(如已修改行数少、持有锁时间短、事务执行时间短)的事务。
死锁检测与牺牲者判定依据
主流数据库(如SQL Server、PostgreSQL、MySQL InnoDB)内置死锁检测器,周期性扫描等待图(Wait-for Graph)。一旦发现环路,立即触发死锁处理流程:
- SQL Server:默认按deadlock priority值高低决定,优先回滚priority更低的事务;若相同,则选回滚开销(estimated rollback cost)更小者
- PostgreSQL:固定选择被阻塞时间最短的事务(即“后来者”),不支持手动配置牺牲策略
- MySQL InnoDB:基于事务持有的行锁数量和事务大小估算回滚代价,选代价最小者;不可配置,但可通过innodb_deadlock_detect开关关闭检测(慎用)
主动控制牺牲倾向的方法
开发者可通过以下方式影响系统对“谁该被回滚”的判断,降低关键事务被选为牺牲者的概率:
- 在事务开始前设置更高优先级(SQL Server):SET DEADLOCK_PRIORITY HIGH;
- 保持事务简短:减少锁持有时间,避免在事务内执行网络调用、用户输入等待等长耗时操作
- 按固定顺序访问表和行:例如始终先更新orders再更新inventory,从源头降低死锁可能性
- 使用低隔离级别(如READ COMMITTED)或行级锁提示(如WITH (UPDLOCK, ROWLOCK)),减少锁粒度和冲突面
应用层应对死锁回滚的健壮设计
即使优化了SQL和事务结构,死锁仍可能偶发。应用必须将死锁异常(如SQL Server的1205错误、MySQL的1213错误)视为可重试的瞬态故障:
- 捕获特定错误码,而非泛化SQLException
- 实现指数退避重试(建议最多3次),避免重试风暴
- 记录死锁事件详情(如sp_who2、sys.dm_exec_requests快照或SQL Server的XML死锁图),用于后续根因分析
- 对无法重试的操作(如已发送消息、已调用外部API),需引入幂等机制或补偿事务
死锁不是Bug,是并发系统的固有现象。重点不在完全消除,而在于让系统快速恢复、关键路径不被误伤、问题可定位可收敛。










