MySQL 死锁分析与定位技巧
在实际业务中,只要涉及 高并发 + 多表更新 + 范围查询 + 索引使用不当,死锁几乎是必然出现的。
死锁会带来:
-
• 请求卡住、超时 -
• 更新失败 -
• 用户投诉 -
• QPS 突然下降
一、什么是死锁?(面试/讲解必备一句话)
两个或多个事务互相等待对方持有的锁,导致所有事务都无法继续执行,这就是死锁。
简单示例:
-
• 事务 A 锁住 row 1,等待 row 2 -
• 事务 B 锁住 row 2,等待 row 1
双方都不释放 → 永远卡住。
InnoDB 会自动检测死锁,挑一个事务回滚,返回:
Deadlock found when trying to get lock
二、为什么会发生死锁?最常见的 6 种场景
-
1. 更新顺序不一致 -
• A: 先更新 user,再更新 order -
• B: 先更新 order,再更新 user
→ 互相等待 -
2. 范围查询 + 记录锁 + 间隙锁 -
• 特别是: WHERE age BETWEEN 10 AND 20 FOR UPDATE -
3. 缺少索引导致锁住更多行 -
• 明明是更新 1 行,却锁住成千上万行 -
4. 大事务未及时提交 -
• 一个事务持有锁时间长 → 只要另一个事务访问重叠范围就容易死锁 -
5. 外键导致隐式锁 -
• 更新父表 -
• 更新子表
→ 额外产生排他锁 -
6. 插入同一个 GAP(插入意向锁) -
• 高频插入容易死锁
你遇到的 90% 死锁都属于以上类型。
三、出现死锁时,第一步要看什么?
MySQL 提供一个神级命令:
SHOW ENGINE INNODB STATUS\G;
其中包含:
-
• 死锁发生时间 -
• 参与死锁的事务 -
• 各事务持有的锁 -
• 正在等待的锁 -
• SQL 语句 -
• 锁的类型(Record/GAP/Next-Key) -
• 索引信息
这一段日志就是排查死锁的核心材料。
四、死锁日志如何看?(最关键)
下面我给你演示一段真实死锁日志,并手把手教你怎么看。
1)锁等待信息
*** (1) TRANSACTION:
WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 123 page no 45 n bits 72 index `idx_age` of table `user`
lock_mode X locks gap
含义:
-
• 事务 1 在等待一个 间隙锁(Gap Lock) -
• 锁位置:索引 idx_age -
• 说明:这个 SQL 是范围查询
2)持有锁的信息
*** (2) TRANSACTION:
HOLDS THE LOCK:
RECORD LOCKS space id 123 page no 45 index `idx_age`
lock_mode X
事务 2 持有锁。
3)关键 SQL
通常在日志底部:
*** (1) WAITING FOR:
UPDATE user SET score = score + 1 WHERE age BETWEEN 10 AND 20;
*** (2) HOLDS THE LOCK(S):
INSERT INTO user (age, name) VALUES (15, 'Tom');
这就非常清楚了:
-
• 事务 1:范围更新(Next-Key Lock) -
• 事务 2:插入(Insert Intention Lock)
引发死锁。
五、通用的死锁定位步骤(你可以直接用)
无论哪个业务,无论哪个库,这 5 步必能定位问题:
① 执行:查看死锁日志
SHOW ENGINE INNODB STATUS\G;
记录:发生地点、索引、锁类型
② 复制出参与死锁的 SQL
把日志中的:
-
• “WAITING FOR” -
• “HOLDS THE LOCKS”
两部分 SQL 全部复制到你的文档里。
③ 根据锁类型判断问题来源
锁类型含义:
④ 检查索引是否正确使用
无索引会导致:
-
• 表扫描 -
• 锁住整个区间 -
• 立刻死锁
如果 SQL 走错索引,基本可以判定这里就是死锁根源。
⑤ 重现死锁,确认优化方案
把业务 SQL 用两个会话复现一遍(加入 SLEEP),100% 可以还原。
六、如何彻底避免死锁?(最重要的部分)
1)固定访问顺序(最有效)
A、B 两个事务更新多表时必须保持顺序一致:
-
• 全按 user → order -
• 或全按 order → user
避免交叉等待,是最强杀手锏。
2)把大事务拆小,尽快提交
不要:
-
• 在事务内 sleep -
• 在事务内执行大量逻辑 -
• 在事务里循环上千次操作
锁持有时间越长 → 死锁率越高。
3)明确加锁范围,避免范围查询
尽量使用:
WHERE id = 10 -- 精确索引命中,不会触发 gap lock
避免:
WHERE id > 10 AND id < 100
4)保持索引设计正确
缺少索引会导致锁住更多行,引发死锁。
重点关注:
-
• WHERE 使用的字段必须有索引 -
• 尽量使用覆盖索引 -
• 避免 MySQL 使用错误的索引(可以加 FORCE INDEX)
5)减少并发写热点
热点行更新导致:
-
• 排他锁冲突 -
• 死锁概率陡增
优化方式:
-
• 拆分热点字段 -
• 把计数操作用 Redis -
• 使用分布式锁分流写请求
6)关闭不必要的 gap lock(使用 RC 隔离级别)
在 RC 下:
-
• Gap Lock 不会锁范围 -
• 死锁大幅减少
设置方式:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
适用于:
-
• 电商 -
• 内容类系统 -
• 高并发业务
(但严重依赖金融强一致性的系统不适用)
七、总结
MySQL 死锁的本质是事务互相等待锁。
InnoDB 会自动检测并回滚其中一个事务。
分析死锁时主要看:索引、锁类型、范围锁、SQL 访问顺序。
解决死锁最有效的方法是:统一访问顺序、建立正确索引、减少范围更新、缩小事务规模。

