死锁不是数据库“坏了”,而是并发事务互相等待对方释放资源,形成了环。理解死锁的条件和处理方式,有助于写出更稳定的事务代码。
活锁和死锁不是一回事
活锁是某个事务一直得不到机会。例如多个事务不断插队获取同一资源,某个早来的事务一直等待。解决思路通常是公平队列或先来先服务。
死锁是多个事务互相等待。事务 A 锁住资源 1,等待资源 2;事务 B 锁住资源 2,等待资源 1,双方都无法继续。
活锁是“总有人在动,但你一直轮不到”;死锁是“大家都卡住了”。
死锁产生需要多个条件
经典条件包括:
- 互斥:资源一次只能被一个事务持有。
- 持有并等待:持有一个资源时继续申请另一个资源。
- 不可抢占:资源不能被外部强行拿走,只能持有者释放。
- 循环等待:多个事务形成等待环。
理论上破坏任意一个条件都能预防死锁,但数据库系统里完全预防通常会牺牲太多并发能力。
数据库通常靠检测和回滚解决
数据库一般不要求所有事务一次性申请全部锁,也很难强制所有业务按固定资源顺序加锁。因此更常见的是检测死锁并选择一个代价较小的事务回滚。
检测方式包括超时和等待图。等待图把事务作为节点,把“谁等待谁”作为边。如果图里出现环,就说明发生死锁。
InnoDB 检测到死锁后,会回滚其中一个事务,让其他事务继续执行。
业务代码要减少死锁概率
虽然数据库能检测死锁,但业务不能完全依赖数据库兜底。常见优化手段有:
- 多个事务按相同顺序访问表和行。
- 更新前确保 where 条件命中索引,避免锁范围扩大。
- 事务里只做必要数据库操作,不夹杂远程调用和耗时计算。
- 批量更新拆小批,减少持锁时间。
- 对可重试操作增加幂等和有限重试。
锁的范围越小,持锁时间越短,死锁概率越低。
排查要看最近一次死锁信息
MySQL 可以通过 InnoDB 状态查看最近一次死锁信息,重点看:
- 哪两个事务参与死锁。
- 各自执行的 SQL。
- 持有哪些锁,等待哪些锁。
- 是否走了索引。
- 锁住的是记录锁、间隙锁还是范围。
不要只看“Deadlock found”这行异常。真正的原因在事务持锁顺序和 SQL 条件里。
重试不是万能药
死锁异常通常可以重试,但重试必须有边界。
适合重试的前提是操作幂等、事务短、失败概率低。不能无限重试,也不能把死锁当成正常高频路径。重试只能降低偶发死锁影响,不能替代 SQL 和事务设计。
实用结论
MySQL 死锁处理要从三个层面看:数据库会检测等待环并回滚一个事务;业务要统一加锁顺序、缩短事务、控制锁范围;应用层可以对幂等操作做有限重试。
死锁不是靠“加大超时时间”解决的。真正有效的改法,通常是改访问顺序、补索引、缩短事务和减少批量锁竞争。




