死锁检测是事务正确性的安全阀,不可关闭;关闭后死锁事务将卡在LOCK WAIT直至超时回滚,实为掩盖问题而非优化性能。

死锁检测不是性能开关,而是事务正确性的安全阀
MySQL 的 innodb_deadlock_detect 不能当作“高并发压测时的性能调优开关”来关。它一旦关闭,Deadlock found when trying to get lock 错误就彻底消失,取而代之的是所有死锁事务卡在 LOCK WAIT 状态,直到 innodb_lock_wait_timeout(默认 50 秒)超时才回滚。你看到的“CPU 下降”,只是把死锁从 10ms 暴露延迟到了 50s 暴露——火没灭,警报器被拆了。
真正吃 CPU 的从来不是检测逻辑本身:lock_deadlock_occurs() 单次调用是纳秒级;问题出在高并发下大量事务反复争抢同一行(比如库存 ID=1),导致等待图节点数暴涨,检测复杂度接近 O(N²)。这不是检测太重,是锁竞争太烈。
什么时候可以考虑关掉 innodb_deadlock_detect?
只有满足全部以下条件时,关检测才不算冒险:
- 业务路径非核心:如日志写入、埋点上报、异步任务触发,允许短暂不一致或失败重试
- 应用层已实现死锁错误捕获 + 指数退避重试(最多 3 次),且能区分
Deadlock found和Lock wait timeout exceeded - 热点数据已分片(如库存按商品类目哈希分表),单行争用被大幅稀释
- 已同步调低
innodb_lock_wait_timeout至 5 秒以内(默认 50 秒不可接受) -
innodb_rollback_on_timeout保持OFF(默认值),否则超时回滚不通知客户端,应用可能误判成功
不满足任意一条,关检测就是拿稳定性换虚假的吞吐数字。
比关检测更有效的三件事
真正降低死锁率和锁等待,优先做这三件实操性强的事:
-
统一加锁顺序:所有事务更新多行时,强制按主键升序(或业务定义的全局顺序)执行,例如先
UPDATE t SET v=v+1 WHERE id=1,再UPDATE t SET v=v+1 WHERE id=2,避免 A→B 和 B→A 的循环 -
缩小锁范围:确保 WHERE 条件命中索引,避免间隙锁扩散;RR 隔离级别下,
SELECT ... FOR UPDATE若无索引会锁全表或整个区间,换成 RC 隔离可消除间隙锁(但需评估幻读风险) - 拆分长事务:把“查余额 → 扣减 → 记流水 → 发消息”这种复合事务,拆成原子操作+补偿机制;尤其避免在事务内做 RPC、文件 IO 或 sleep
线上死锁排查必须盯住的两个指标
别只看 SHOW ENGINE INNODB STATUS 里的 LATEST DETECTED DEADLOCK —— 它只保留最近一次。真正反映系统健康度的是:
-
innodb_row_lock_waits:每秒增长 > 10 次,说明锁争用已成常态,不是偶发 -
innodb_row_lock_time_avg:如果这个值很低(比如 innodb_row_lock_waits 很高,说明不是锁持有久,而是抢锁失败太频繁——根因是热点行+无序访问,不是检测开销
这时候去调 innodb_deadlock_detect,就像给发烧病人量血压却不退烧。先定位哪张表、哪几行、哪类 SQL 在高频争抢,再动索引或事务逻辑。


















