MySQL死锁本质是多个事务循环等待对方持有的锁,InnoDB检测后回滚undo量小的事务;典型场景包括多表更新顺序不一致、非唯一索引间隙锁冲突、范围查询与插入混合、未走索引致全表加锁。

MySQL 死锁本质是多个事务互相等待对方持有的锁,形成循环等待,导致所有相关事务都无法继续执行。InnoDB 引擎在检测到死锁后会自动选择一个事务回滚(通常是 undo log 量小的那个),释放其锁,让其他事务继续。
死锁产生的典型场景
死锁不是偶然,而是特定并发操作模式下的必然结果。常见诱因包括:
- 多表交叉更新顺序不一致:事务 A 先更新表 user 再更新 order;事务 B 反过来先更新 order 再更新 user。若两个事务恰好在各自第一步加锁后并发执行第二步,就可能锁住对方需要的行,形成死锁。
-
非唯一索引上的间隙锁冲突:例如对 name 字段(无唯一约束)执行
SELECT ... FOR UPDATE WHERE name = 'Alice',InnoDB 不仅锁匹配行,还锁住该值所在间隙。两个事务对同一间隙加锁时易触发死锁。 -
范围查询 + 插入/更新混合操作:如事务中先
SELECT ... FOR UPDATE WHERE id > 100,再插入一条 id=105 的记录,可能与另一个正在插入或更新附近主键值的事务发生间隙锁竞争。 - 未使用索引导致全表扫描加锁:WHERE 条件未命中索引时,InnoDB 可能对整张表的聚簇索引记录逐条加锁,大幅增加锁冲突概率,尤其在高并发下极易引发死锁。
快速定位死锁信息
MySQL 默认开启死锁检测,发生后会在错误日志(error log)中记录详细信息。关键操作如下:
-
查看最近一次死锁详情:执行
SHOW ENGINE INNODB STATUS\G,重点关注 LATEST DETECTED DEADLOCK 区块,里面包含两个(或多个)事务的 SQL、所持锁、等待锁、事务ID、隔离级别、undo log 大小等。 -
确认死锁是否频繁发生:检查 MySQL 错误日志路径(由
log_error参数指定),用grep -i "deadlock" /path/to/error.log统计近期出现次数。 -
开启死锁日志增强分析(MySQL 8.0.24+):设置
innodb_print_all_deadlocks = ON,可将每次死锁都写入 error log(默认只记录最后一次),便于持续监控。
预防与优化建议
治标更要治本。减少死锁的核心是降低锁粒度、统一访问顺序、缩短事务时间:
-
按固定顺序访问多表或多行:例如约定所有业务逻辑先操作 user 表、再操作 order 表;或对批量更新的 ID 列预先排序(
ORDER BY id),保证加锁顺序一致。 -
为 WHERE 条件字段添加合适索引:避免全表扫描加锁,尤其注意联合索引的最左匹配原则。可用
EXPLAIN验证查询是否走索引。 - 减少事务内操作数量和耗时:不在事务中做 RPC 调用、文件读写、复杂计算;把非数据一致性必需的操作移出事务;及时提交或回滚。
-
合理选用隔离级别:多数场景
READ COMMITTED比默认的REPEATABLE READ更少产生间隙锁,从而降低死锁概率(但需评估幻读影响)。
应用层应对策略
即使做了充分优化,极端并发下仍可能偶发死锁。应用应具备容错能力:
-
捕获死锁异常并重试:MySQL 返回错误码
1213 (ER_LOCK_DEADLOCK)。应用应在事务外层捕获该错误,短暂休眠(如 10–100ms)后重试,通常 1–2 次即可成功。 - 避免无限重试或重试风暴:设置最大重试次数(如 3 次),超限则抛出业务异常;对高频写接口考虑加分布式锁或队列削峰。
- 记录死锁上下文用于复盘:在重试前记录触发死锁的用户ID、接口名、参数摘要、时间戳,方便后续关联分析。










