排查死锁
目录
- 一、排查死锁的核心思路
- 二、排查流程
- 三、复盘(死锁排查步骤)
- 四、小结
一、排查死锁的核心思路
死锁排查的本质,就是通过死锁日志,还原出两个事务的加锁顺序和等待关系,找到循环等待的环路。
核心步骤:
-
看日志:通过
SHOW ENGINE INNODB STATUS;拿到死锁日志。 -
拆日志:把日志拆成两部分:
- 事务 1:持有什么锁?在等什么锁?
- 事务 2:持有什么锁?在等什么锁?
-
画流程:按时间线还原两个事务的执行顺序,找到循环等待的环路。
-
定原因:根据环路,定位到是哪两条 SQL、哪种锁导致了死锁。
二、排查流程
1、拿到死锁日志
执行命令:
SHOW ENGINE INNODB STATUS;
重点关注 LATEST DETECTED DEADLOCK 部分。
2、拆解日志(关键!)
配合本节内容食用
死锁形成流程
| 步骤 | 事务 1 | 事务 2 |
|---|---|---|
| 1 | begin; | |
| 2 | delete from test where a = 2; → 成功,在索引 a 上获得 a=2 的 X 型记录锁 | |
| 3 | begin; | |
| 4 | delete from test where a = 2; → 阻塞,尝试申请 a=2 的 X 锁,但事务 2 已经持有 X 锁,X 锁互斥,所以等待 | |
| 5 | insert into test (id,a) values (10,2); → 阻塞,因为 a 是唯一索引,插入前需要申请 S 锁 做重复检查,但事务 1 正在等待 X 锁,S 锁和 X 锁互斥,形成循环等待,死锁发生。事务 1 权重小,被回滚。 |
事务 1(TRANSACTION 2A8BD)
- 执行的 SQL:
delete from <test> where a = 2; - 当前状态:在等待一个锁(WAITING FOR THIS LOCK TO BE GRANTED)
- 等待的锁:在索引
a上的 X 锁(记录锁) ,锁的是a=2的记录。
事务 2(TRANSACTION 2A8BC)
- 执行的 SQL:
insert into <test> (id,a) values (10,2); - 当前状态:持有一个锁(HOLDS THE LOCK (S)),同时在等待另一个锁
- 持有的锁:在索引
a上的 X 锁(记录锁) ,锁的是a=2的记录。 - 等待的锁:在索引
a上的 S 锁(共享锁) 。
3、 定位死锁原因
直接原因:两个事务交叉申请了 X 锁和 S 锁,形成了循环等待。
三、复盘(死锁排查步骤)
-
获取死锁日志:
SHOW ENGINE INNODB STATUS;找到
LATEST DETECTED DEADLOCK部分。 -
分析每个事务
- 事务 ID、执行时间
- 执行的 SQL 语句
- 持有哪些锁(
HOLDS THE LOCK(S)) - 在等待哪些锁(
WAITING FOR THIS LOCK TO BE GRANTED) - 锁的类型:X 锁、S 锁、记录锁、间隙锁、临键锁
-
画出等待关系图
- 事务 A 持有锁 L1,等待锁 L2
- 事务 B 持有锁 L2,等待锁 L1
- 这样就形成了循环等待,死锁就发生了。
-
定位问题 SQL 和场景
- 是交叉加锁?
- 是间隙锁 + 插入意向锁?
- 是唯一索引检查导致的 S 锁和 X 锁冲突?
-
给出解决方案
- 调整事务内 SQL 的执行顺序
- 降低隔离级别(从 RR 到 RC)
- 优化索引,避免全表扫描导致的大范围锁
- 让事务更短,尽快提交释放锁
四、小结
从死锁日志里找出“谁持有什么锁、在等什么锁”,然后按时间线还原出循环等待的环路,最后定位到问题 SQL 和场景。