mysql死锁检测机制通过构建等待图识别循环等待并自动回滚代价小的事务;关闭innodb_deadlock_detect后死锁不会消失,仅延迟为锁超时错误,且默认仅回滚语句而非整个事务,易导致业务逻辑错乱。

MySQL 的死锁检测机制不能也不该被关闭——它不是性能开关,而是事务一致性的安全阀。关掉 innodb_deadlock_detect=OFF 后,死锁不会消失,只会变成 50 秒后才暴露的超时错误,应用层收不到 Deadlock found when trying to get lock,而是卡住半分钟再报 Lock wait timeout exceeded。
死锁检测是怎么工作的?
InnoDB 在每次加锁请求触发等待时,会构建一个“等待图”(wait-for graph),节点是事务,边是“事务 A 等待事务 B 持有的锁”。如果图中出现环(比如 A→B→A),就判定为死锁。此时引擎自动选择 undo 日志量更小、回滚代价更低的事务进行回滚,并释放其所有锁,让其他事务继续执行。
这个过程由函数 lock_deadlock_occurs() 实现,递归深度上限是 lock_max_depth_in_deadlock_check=200,搜索步数上限是 lock_max_n_steps_in_deadlock_check=1000000。单次检测开销在纳秒级,真正拖慢 CPU 的,是高并发下大量事务反复争抢同一行导致等待图急剧膨胀。
为什么有人想关死锁检测?
现象上,关掉后 CPU 使用率可能下降,但本质是把“立刻失败 + 明确错误”掩盖成“长时间无响应 + 模糊超时”。常见误判场景包括:
- 看到
SHOW PROCESSLIST里一堆Locked或updating状态,误以为是检测本身吃资源 - 没意识到
innodb_row_lock_waits持续上涨 +innodb_row_lock_time_avg很低,说明是抢锁失败太频繁,而非锁持有久 - 把热点行争用、索引缺失、事务顺序混乱等问题,错当成“检测太耗 CPU”来优化
关了之后到底会发生什么?
当 innodb_deadlock_detect=OFF 且 innodb_rollback_on_timeout=OFF(默认值)时,事务不会被主动回滚,只回滚当前语句,事务本身仍处于活跃状态,连接未断,后续 SQL 继续执行——但很可能还在等那个永远得不到的锁。
例如两个事务交叉更新同一行:
session1: UPDATE t SET x=1 WHERE id=1; -- 成功 session1: UPDATE t SET x=2 WHERE id=2; -- 等 session2 的锁 <p>session2: UPDATE t SET x=3 WHERE id=2; -- 成功 session2: UPDATE t SET x=4 WHERE id=1; -- 等 session1 的锁</p>
关检测后,两者都卡住,直到 50 秒超时;而超时后仅回滚最后一条语句,事务仍开着,后续操作可能产生脏写或逻辑错乱。
真正该做的不是关检测,而是压根避免死锁发生
检测只是兜底,不是解药。高频死锁背后一定是可优化的设计问题:
- 所有涉及多行更新的事务,必须按统一顺序访问(比如总是先
id小的,再id大的) - 确保
WHERE条件走索引,避免全表扫描引发大面积行锁或间隙锁扩散 - 缩短事务生命周期:不要在事务里做 RPC、sleep、复杂计算
- 对热点数据(如库存、计数器)做分片或改用乐观锁 + 重试
- 应用层捕获
ERROR 1213 (40001),配合指数退避重试(最多 3 次)
最危险的误区,是把死锁当成“数据库的问题”,然后去调参数;实际上它几乎总是业务逻辑和 SQL 写法暴露出来的信号——那个被回滚的事务,往往就是最先暴露设计缺陷的那个。











