MySQL 8.0死锁检测机制未变,仍基于wait-for graph;真正降低开销靠避免触发检测,如用SKIP LOCKED跳过已锁行、设innodb_autoinc_lock_mode=2减少AUTO_INC锁争用,而非关闭deadlock_detect。

MySQL 8.0 的死锁检测机制本身没变,但可绕过它
MySQL 8.0 并未重写死锁检测算法(仍是基于 wait-for graph 的深度优先遍历),INFORMATION_SCHEMA.INNODB_TRX 和 INFORMATION_SCHEMA.INNODB_LOCK_WAITS 查看锁状态的开销也没显著降低。真正能“减少锁检测开销”的,是避免触发检测——也就是让事务不进入死锁检测路径。
- 死锁检测只在事务尝试获取行锁失败、且等待队列中存在其他等待者时才启动;如果事务能立即获得锁,或明确知道不会冲突,检测根本不会发生
-
innodb_deadlock_detect默认为ON,但它不是性能开关,而是功能开关:关掉后,死锁不会被自动发现,而是靠超时(innodb_lock_wait_timeout)回滚,这会掩盖问题而非优化 - 高频更新热点行(如计数器、余额)是死锁检测开销飙升的主因,因为大量线程在同几个记录上循环等待
用 SELECT ... FOR UPDATE SKIP LOCKED 规避争抢
这是 MySQL 8.0 引入的最实用的死锁减负手段,适用于任务队列、分页取数、库存预占等场景。它不消除锁,但让并发线程跳过已被加锁的行,从而彻底避开等待和后续的死锁检测。
- 传统
SELECT ... FOR UPDATE LIMIT 1在高并发下极易形成“所有线程都卡在第一行”,触发密集的死锁检测 -
SKIP LOCKED让每个线程拿到一个**未被锁定的可用行**,事务之间无交集,自然无等待、无图遍历、无检测开销 - 注意:它只对
READ COMMITTED或REPEATABLE READ有效,且不能用于UPDATE ... WHERE的子查询中(会报错ER_NOT_SUPPORTED_YET) - 示例:
SELECT id, status FROM tasks WHERE status = 'pending' ORDER BY id LIMIT 1 FOR UPDATE SKIP LOCKED;
比SELECT ... FOR UPDATE+ 应用层重试快一个数量级,且不增加死锁检测压力
用 innodb_autoinc_lock_mode = 2 消除 AUTO_INCREMENT 锁瓶颈
大批量插入(如 INSERT ... SELECT、批量导入)在 innodb_autoinc_lock_mode = 1(默认)下会持表级 AUTO-INC 锁,阻塞其他插入并拉长锁等待链,间接放大死锁检测负担。
- 设为
2(interleaved 模式)后,InnoDB 仅对简单 INSERT 预分配连续 ID,其余语句(包括批量插入)不再阻塞,锁粒度从“表级瞬时”降为“行级无等待” - 前提:必须使用
READ-COMMITTED隔离级别,否则可能破坏语句级复制一致性(MySQL 5.7+ 已支持该组合) - 验证是否生效:
SHOW VARIABLES LIKE 'innodb_autoinc_lock_mode';返回2,且慢日志中AUTO_INC相关等待消失 - 副作用:主从复制若用 STATEMENT 格式,需切为 ROW 格式,否则从库可能因 ID 不一致报错
别依赖 innodb_deadlock_detect = OFF 来“提速”
关掉死锁检测看似省 CPU,实则把问题延后到超时回滚,导致请求毛刺加剧、错误难定位、业务逻辑更难兜底。
- 一旦关闭,两个互相等待的事务会各自等到
innodb_lock_wait_timeout(默认 50 秒)才回滚,期间持续占用连接、内存和锁资源 - 监控上表现为大量
Locked状态的State字段(查INFORMATION_SCHEMA.PROCESSLIST),而不是清晰的死锁日志 - 唯一适用场景:极低频、可容忍分钟级延迟的后台批处理,且已确认无循环依赖风险——但这不属于“高并发优化”,而是妥协方案
- 真正要压测的参数是
innodb_lock_wait_timeout(调低至 5–10 秒)和应用层重试策略,而非关检测
SKIP LOCKED 避让、按主键顺序更新。MySQL 8.0 提供了更好的工具,但没提供逃避设计责任的捷径。


















