MySQL 8.0死锁报错激增并非死锁变多,而是默认开启且更激进的innodb_deadlock_detect将原被静默忽略的环形等待全部检测并中止;5.7中该参数常被手动关闭,而8.0默认ON,通过死锁图遍历主动回滚,与innodb_lock_wait_timeout超时机制无关。

MySQL 8.0 默认开启 innodb_deadlock_detect,不是“变多”而是“全报了”
你看到的 Deadlock found when trying to get lock 错误激增,大概率不是死锁真的变多了,而是 MySQL 8.0 把原来被静默忽略的环形等待全检测并中止了。5.7 中这个参数默认是 OFF(尤其高并发写场景常被手动关),而 8.0 默认为 ON。它和 innodb_lock_wait_timeout 完全无关——后者只管“等锁超时”,前者才是真正在做死锁图遍历和主动回滚。
常见错误现象:
升级后错误日志里密集出现死锁,但业务逻辑没改;
同一组表、相同 WHERE 条件反复出现在不同事务的 HELD LOCKS 和 WAITING FOR THIS LOCK TO BE GRANTED 中;
事务回滚后应用重试,又立刻触发新死锁。
- 别急着关
innodb_deadlock_detect:关掉它只会让问题更隐蔽,最终表现为连接堆积、响应延迟、甚至事务卡死 - 检查是否真有循环等待:用
SHOW ENGINE INNODB STATUS\G看LATEST DETECTED DEADLOCK区块,确认两个事务是否一个持有 A 表锁等待 B 表,另一个反之 - 如果确实只是“误报”(极少见),可临时关掉:
SET GLOBAL innodb_deadlock_detect = OFF,但必须配套监控INFORMATION_SCHEMA.INNODB_TRX中长期未提交事务
死锁日志里反复出现相同表和索引?说明加锁顺序不一致
死锁本质是多个事务以不同物理顺序访问同一组行。比如事务 A 先 UPDATE t1 WHERE id = 1 再 UPDATE t2 WHERE order_no = 'xxx',事务 B 反过来先 t2 后 t1;或者批量更新时,WHERE id IN (3,1,2) 和 WHERE id IN (2,3,1) 导致加锁顺序天然不同。
使用场景:
转账、库存扣减、优惠券核销等高频并发更新热点行;
ORM 自动生成的 IN 列表未排序;
分页查询 + 更新混合操作(如先查再按 ID 更新)。
- 统一 ID 排序:应用层生成 SQL 前对参数列表
ORDER BY id,哪怕只更新一行也强制加ORDER BY id ASC - 避免 ORM 自动拼 IN:禁用 MyBatis 的
<foreach>无序遍历,改用预排序集合 - 热点行加锁强制顺序:对账户余额类场景,用
SELECT ... FOR UPDATE ORDER BY id ASC,即使只锁一行也生效
升级后 sys.innodb_lock_waits 查询变慢甚至阻塞?MDL 锁行为已变
MySQL 8.0 中 sys.innodb_lock_waits 不再是纯视图,它底层依赖 performance_schema.data_locks 和 data_lock_waits,读取时会申请 MDL 锁。如果此时恰有 DDL(如 ALTER TABLE)在执行,监控脚本就会卡在 Waiting for table metadata lock,拖慢连接池,间接推高锁等待和死锁感知密度。
容易踩的坑:
运维脚本每 5 秒轮询 sys.innodb_lock_waits;
未限制 lock_wait_timeout,导致监控连接长时间挂起;
DDL 与业务高峰重叠,放大阻塞效应。
- 改用轻量替代方案:直接查
INFORMATION_SCHEMA.INNODB_TRX+INNODB_LOCKS(8.0.29+ 已移除,需降级兼容)或performance_schema.data_locks(注意权限) - 设好元数据锁超时:
SET SESSION lock_wait_timeout = 10,避免监控自己变成阻塞源 - DDL 尽量避开业务高峰,并确认
metadata_locks_cache_size足够(默认 1024,高并发建议调至 2048)
为什么优化索引顺序能直接降低死锁频率
索引字段顺序决定执行计划是否走索引、锁多少行。例如联合索引 (pool_id, identifier, status),若 identifier 允许为 NULL,且查询条件是 WHERE pool_id = ? AND status = ?,InnoDB 可能无法跳过中间字段,最终只用到 pool_id 前缀,引发大范围间隙锁(X locks gap before rec),多个事务锁集重叠概率飙升。
性能影响:
没走索引的 UPDATE 会全表扫描加 X 锁,锁行数从几行涨到几千行;
间隙锁范围扩大后,INSERT/UPDATE 邻近值极易冲突;
不同事务因执行路径不同(索引选择差异),加锁顺序天然分裂。
- 高频等值字段放最左:如总按
pool_id和status查询,建(pool_id, status, is_redeemed) - 范围查询字段放末尾:
created_at > ?必须放在联合索引最后,否则后续字段失效 - 验证是否生效:开
innodb_print_all_deadlocks = ON,比对死锁日志中lock_mode是否从X locks gap before rec变成X locks rec but not gap
真正难处理的不是参数调整,而是那些 SELECT FOR UPDATE 后忘了 COMMIT 的僵尸事务——它们在 5.7 下可能“侥幸存活”,在 8.0 下会被快速暴露、连锁拖垮。查 INFORMATION_SCHEMA.INNODB_TRX 里 trx_started 时间过早的记录,比调任何参数都管用。


















