MySQL死锁是多个事务以不一致顺序争抢行锁或间隙锁导致的循环等待,InnoDB会回滚undo量小的事务;最常见原因是多表/多行更新加锁顺序不一致(占比超60%),其次为索引缺失引发全表扫描加锁、RR隔离级间隙锁冲突、长事务放大死锁风险及ORM动态SQL顺序不可控。

MySQL死锁不是随机故障,而是多个事务以不一致顺序争抢行锁或间隙锁时必然出现的结果;InnoDB 检测到循环等待后会回滚 undo 量小的事务,但频繁发生说明业务逻辑或索引设计存在硬伤。
死锁最常出现在多表/多行交叉更新顺序不一致时
这是生产环境里占比超 60% 的死锁原因。两个事务操作相同资源(比如 user 和 order 表),但加锁顺序相反:事务 A 先锁 user.id=1 再锁 order.order_id=100,事务 B 反过来先锁 order.order_id=100 再锁 user.id=1,只要并发稍高,立刻形成循环等待。
实操建议:
- 所有涉及多表更新的业务,必须约定全局加锁顺序(例如“先
user后order再payment”),并在代码注释和 DBA 文档中明确写死 - 批量更新单表多行时,
WHERE id IN (…)中的 ID 必须提前ORDER BY id排序,否则 MySQL 内部加锁顺序不可控(即使IN列表乱序,InnoDB 也会按主键升序加锁,但你不能依赖这个行为) - 避免在事务中拼接动态 SQL 或由 ORM 自动决定 where 条件顺序——GORM、MyBatis 等框架若未显式控制字段顺序,极易引发隐式死锁
索引缺失或失效导致全表扫描加锁
当 WHERE 条件没走索引,InnoDB 会逐条扫描聚簇索引并加行锁,相当于对整张表加了大量行锁。此时哪怕只是两个简单 UPDATE,也可能因锁住不同行却互相等待而死锁。
常见错误现象:
- 执行
EXPLAIN显示type = ALL或key = NULL - 慢查询日志里出现
Rows_examined远大于实际匹配行数 - 死锁日志中显示锁类型为
X(排他锁)但锁定范围是成百上千行
实操建议:
- 对所有高频
WHERE字段建索引,联合索引注意最左前缀原则(例如WHERE status = ? AND created_at > ?,应建(status, created_at)而非(created_at, status)) - 禁止在索引字段上做函数操作:
WHERE DATE(create_time) = '2026-07-01'会跳过索引,改用WHERE create_time >= '2026-07-01' AND create_time - 定期用
pt-index-usage或performance_schema.table_io_waits_summary_by_index_usage检查索引实际使用率,删掉长期未被命中的冗余索引
用 SHOW ENGINE INNODB STATUS\G 快速定位最近一次死锁
这是排查死锁最直接有效的命令,输出中 LATEST DETECTED DEADLOCK 区块包含完整上下文:谁在执行什么 SQL、持有哪些锁、等待哪些锁、事务隔离级别、undo log 大小等。
关键信息解读:
- 看
*** (1) TRANSACTION:和*** (2) TRANSACTION:对应的mysql tables in use和locked tables,确认是否跨表 - 找
lock_mode X locks rec but not gap(记录锁)、lock_mode X locks gap before rec(间隙锁)、lock_mode X locks rec but not gap waiting(正在等待的锁) - 比对两个事务的
ROLLING BACK提示——被回滚的是 undo 量小的那个,但它未必是“错”的那个,只是 InnoDB 的选择策略
注意:SHOW ENGINE INNODB STATUS 只保留最后一次死锁,线上需配合开启 innodb_print_all_deadlocks = ON,把每次死锁都写入 error log(路径由 log_error 配置)。
间隙锁(Gap Lock)在 RR 隔离级别下极易引发死锁
REPEATABLE READ 是 InnoDB 默认隔离级别,它会用 Next-Key Lock(记录锁 + 间隙锁)防止幻读。但如果你在非唯一索引上执行范围查询或等值查询(如 SELECT ... FOR UPDATE WHERE name = 'Alice'),InnoDB 不仅锁匹配行,还会锁住该值所在间隙。两个事务同时尝试插入或更新同一间隙附近的数据,就可能触发死锁。
实操建议:
- 对
name这类高频查询字段加唯一索引,可消除间隙锁(唯一索引的等值查询只加记录锁) - 若业务允许,将隔离级别降为
READ COMMITTED(需评估一致性风险),该级别下间隙锁基本不生效 - 避免在事务中混合执行
SELECT ... FOR UPDATE范围查询 +INSERT相邻主键值的操作,例如先SELECT ... WHERE id > 100 FOR UPDATE,再INSERT INTO t VALUES (105, ...)
真正棘手的从来不是单次死锁报错,而是那些没被日志捕获、靠应用重试掩盖过去的“幽灵死锁”——它们往往藏在长事务、隐式锁、或 ORM 自动生成的不可见 SQL 里。盯紧 innodb_trx 视图里的 trx_started 时间,超过 5 秒的活跃事务就得人工介入。


















