MySQL死锁检测不可关闭,它是保障事务正确性的安全阀;关闭后死锁不被发现,事务将等待超时(默认50秒)才回滚,导致响应延迟、线程堆积和错误掩盖,真正根因是热点行争用、索引缺失与事务设计缺陷。

MySQL 的死锁检测不能也不该被“建议关闭”——它不是性能开关,而是事务正确性的安全阀。所谓“高并发下建议关闭”,是把症状当病因、用掩盖代替治理的危险误解。
innodb_deadlock_detect=OFF 真的能提升性能?
关掉后 CPU 使用率可能确实下降,但代价是:所有死锁不再被主动发现,事务会卡在 LOCK WAIT 状态,直到 innodb_lock_wait_timeout(默认 50 秒)超时才回滚。你看到的“快”,只是错误被延迟暴露了。
- 应用层收不到
Deadlock found when trying to get lock,而是等半分钟才收到Lock wait timeout exceeded - 活跃等待线程堆积,
SHOW PROCESSLIST里大量updating或Locked状态,实际已无进展 -
SHOW ENGINE INNODB STATUS中LATEST DETECTED DEADLOCK消失,但TRANSACTIONS列表里 waiting 数持续上涨
真正吃 CPU 的从来不是检测逻辑本身
死锁检测函数 lock_deadlock_occurs() 的单次调用开销是纳秒级的。真正让 CPU 爆表的,是高并发下大量事务反复争抢同一行(比如库存扣减、计数器更新),导致每次加锁都要遍历一个越来越大的等待图(wait-for graph)——这个图的节点数 ≈ 活跃事务数,边数 ≈ 锁等待链长度,检测复杂度接近 O(N²)。
- 现象:CPU 100% 时,
innodb_row_lock_waits持续上涨,但innodb_row_lock_time_avg很低(说明不是锁持有久,而是抢锁失败太频繁) - 根因不是检测,而是热点行 + 无序访问 + 间隙锁扩散(尤其在
REPEATABLE READ隔离级别下) - 关检测就像拔掉警报器来“解决”火灾报警声——火还在烧
为什么有人觉得“关了更稳”?
那通常是因为他们已在外围做了足够强的兜底:应用层捕获死锁错误并指数退避重试(最多 3 次)、事务粒度极小、热点数据已分片、且业务允许短暂不一致(如浏览量、点赞数)。此时关检测只是把“10ms 失败 + 重试”换成“50s 超时 + 重试”,对整体成功率影响不大,却避免了检测 CPU 雪崩拖垮整个实例。
- 这种做法只适用于非核心路径(如日志写入、异步触发),绝不适用于支付、订单、账户余额等强一致性场景
- 关检测必须配合
innodb_lock_wait_timeout=5(而非默认 50),否则用户感知延迟不可控 -
innodb_rollback_on_timeout必须保持OFF(默认值),否则超时回滚不通知客户端,应用可能误判操作成功
比开关参数重要一百倍的事
真正决定死锁频率的,永远是 SQL 怎么写、索引怎么建、事务怎么切、数据怎么分片。
- 确保所有
UPDATE/SELECT FOR UPDATE的WHERE条件都命中索引,避免全表扫描触发间隙锁 - 多行更新强制按主键升序处理:
SELECT id FROM t WHERE id IN (1002,1001) ORDER BY id FOR UPDATE - 把热点行拆成多个逻辑单元(如用
MOD(user_id, 64)分 64 个计数器 shard) - 把高频单行更新合并为批量语句:
INSERT INTO t VALUES (...), (...) ON DUPLICATE KEY UPDATE ... - 非必要不升级隔离级别:
READ COMMITTED下间隙锁关闭,锁范围更小,死锁概率天然更低
死锁不是 bug,是并发访问模式与锁机制碰撞出的信号灯。盯着 innodb_deadlock_detect 调参数,不如花十分钟看一眼 SHOW ENGINE INNODB STATUS 里的等待图链条——那里面藏着最真实的业务逻辑缺陷。











