innodb_deadlock_detect是布尔开关而非阈值,仅支持on/off两种状态,mysql 5.7.15引入,默认on;它在事务加锁失败进入lock_wait瞬间按需触发死锁检测,非定时轮询,8.0.22+才支持动态设off,旧版本该变量只读。

innodb_deadlock_detect 不是“阈值”,它只有 ON/OFF 两种状态
很多人搜“死锁检测阈值”,其实是被误导了。innodb_deadlock_detect 是个布尔开关,不是数字参数——它不接受毫秒、百分比或任何中间值。MySQL 5.7.15 引入该参数时设计为 ON(默认)或 OFF,仅此而已。所谓“调灵敏度”“设频率”“改阈值”,在 InnoDB 层面根本不存在。
真正触发检测的时机是:事务尝试加锁失败、进入 LOCK_WAIT 状态的**那一瞬间**,InnoDB 就会构建 wait-for graph 并检查环路。这个过程是即时的、按需的,不是定时轮询。
- MySQL 8.0.22+ 支持
SET GLOBAL innodb_deadlock_detect = OFF,但旧版本(如 5.7、8.0.21 及之前)该变量是只读的,执行会报错:ERROR 1238 (HY000): Variable 'innodb_deadlock_detect' is a read only variable - 即使能设为
OFF,也**不是为了“避免高 CPU”而随便关的**——它只在两个前提同时满足时才考虑:所有事务严格按相同顺序访问表,且CPU 已成为瓶颈(比如单核满载、wait-for graph 遍历开销超预期) - 关掉后,
SHOW ENGINE INNODB STATUS中的LATEST DETECTED DEADLOCK段将彻底消失,你再也看不到死锁现场记录
高 CPU 真正来源:等待图遍历开销,不是“检测太勤”
当并发线程多、锁等待链长、热点行竞争激烈时,InnoDB 构建和遍历 wait-for graph 的成本会上升——这不是因为“检测频率高”,而是因为每次检测要扫描大量锁和事务关系。例如,1000 个等待线程可能触发百万级边遍历,CPU 就顶不住了。
典型诱因包括:
- 事务中先锁
id=2再锁id=1,另一事务反向操作 → 死锁环直接生成,检测必跑 - 用
SELECT ... FOR UPDATE锁住大量无关行(比如全表扫描后加锁),扩大等待图规模 - 长时间持有锁:比如事务里调外部 HTTP 接口、做复杂计算,导致锁 A 持有几十秒,期间上百个请求排队等它 → 等待图节点暴增
- 热点行更新(如影院账户余额)+ 两阶段锁协议 → 锁从第一条 UPDATE 开始,直到 COMMIT 才释放,等待队列雪球式增长
比关检测更有效:减少检测被触发的次数和单次代价
与其冒险关掉 innodb_deadlock_detect,不如从源头压缩它的运行压力。这些改动见效快、风险低:
-
统一锁顺序:所有业务代码按主键升序更新,例如总是
UPDATE ... WHERE id IN (1,2)而不是(2,1);跨表操作固定顺序,如先users后orders -
缩短锁持有时间:把非 DB 操作(日志打点、RPC 调用、JSON 序列化)移出事务;避免在事务里
SLEEP()或等待用户输入 -
降锁粒度:能用
SELECT ... LOCK IN SHARE MODE就不用FOR UPDATE;对计数类场景,改用version字段 +UPDATE ... WHERE version = ?乐观锁 -
拆分热点:余额类字段不要单存一个值,按哈希(如
user_id % 16)分到 16 行,写请求分散
示例:下面这段极易引发死锁且推高 CPU
START TRANSACTION; UPDATE accounts SET balance = balance - 100 WHERE id = 2; UPDATE accounts SET balance = balance + 100 WHERE id = 1; COMMIT;
改成固定顺序即可根治:
START TRANSACTION; UPDATE accounts SET balance = balance - 100 WHERE id = 1; -- 小 ID 优先 UPDATE accounts SET balance = balance + 100 WHERE id = 2; COMMIT;
监控和验证:别猜,要看真实等待图和日志
发现 CPU 高,第一反应不该是“赶紧关死锁检测”,而是确认是否真由检测引发:
- 查
SHOW ENGINE INNODB STATUS\G,重点看LATEST DETECTED DEADLOCK段——如果频繁出现,说明死锁本身高频,得修业务逻辑;如果为空但 CPU 高,大概率是其他原因(如慢查询、刷脏页) - 启用
innodb_print_all_deadlocks = ON(MySQL 8.0.22+ 可动态设),确保每次死锁都记入 error log,方便回溯模式 - 开慢日志:
SET GLOBAL long_query_time = 0; SET GLOBAL log_output = 'TABLE';,然后查mysql.slow_log中含Lock_wait的记录,看哪些语句长期卡在锁上 - 注意:
innodb_lock_wait_timeout默认 50 秒,高并发建议压到 5 或 10;但设得太小会导致正常等待也被误杀,需结合业务容忍度权衡
最后提醒一句:innodb_deadlock_detect = OFF 后,死锁不会“自动消失”,只是变成“等超时才回滚”,用户感知是卡几秒后突然失败——这种体验比立刻报错更差,且问题更难定位。真正要动这个开关,必须已穷尽所有锁顺序、事务拆分、热点隔离手段。











