MySQL 死锁分析思路¶
一、什么是死锁¶
两个或多个事务互相持有对方需要的锁,形成循环等待。
MySQL 检测到死锁后,会选一个事务作为牺牲者回滚。
二、死锁产生的必要条件¶
- 互斥:锁同一时刻只能被一个事务持有。
- 持有并等待:持有一把锁的同时等待另一把。
- 不可剥夺:锁只能由持有者主动释放。
- 循环等待:形成环形等待链。
三、常见死锁场景¶
1. 不同顺序更新同一组行¶
-- 事务 A
BEGIN;
UPDATE account SET balance = balance - 100 WHERE id = 1;
UPDATE account SET balance = balance + 100 WHERE id = 2;
-- 事务 B
BEGIN;
UPDATE account SET balance = balance - 50 WHERE id = 2;
UPDATE account SET balance = balance + 50 WHERE id = 1;
解决:所有事务按相同顺序访问行(如按 id 升序)。
2. 间隙锁(Next-Key Lock)冲突¶
可重复读隔离级别下,范围查询会加间隙锁:
-- 表 t 有 id 1, 5, 10
-- 事务 A
BEGIN;
SELECT * FROM t WHERE id > 5 AND id < 10 FOR UPDATE;
-- 锁住 (5, 10) 这个间隙
-- 事务 B
BEGIN;
INSERT INTO t(id) VALUES (7); -- 阻塞
3. 唯一索引插入冲突¶
两个事务同时插入同一唯一键,都会尝试加锁,互相等待。
四、如何排查¶
1. 查看最近死锁日志¶
在 LATEST DETECTED DEADLOCK 段会打印:
- 持有锁的事务。
- 等待锁的事务。
- 两边执行的 SQL。
- 回滚了哪个事务。
2. 打开锁监控¶
死锁会写到 error log。
3. 查看当前锁等待¶
五、如何避免¶
- 业务上按相同顺序访问表和行。
- 大事务拆小:事务越长,持锁时间越久。
- 索引完善:没有索引会锁全表。
- 降低隔离级别:RC 不会加间隙锁(RR 才有)。
- 避免在事务中交互等待外部资源。
六、死锁 vs 锁等待¶
| 死锁 | 锁等待 | |
|---|---|---|
| 表现 | 互相等待,自动检测 | 单向等待 |
| 超时时间 | 立即检测 | innodb_lock_wait_timeout(默认 50s) |
| 处理 | 自动回滚一个 | 超时后报错 |
线上排查流程
- 拿到死锁日志,找两边 SQL。
- 分析 WHERE 条件走了什么索引、锁了哪些行。
- 调整访问顺序或 SQL,必要时改隔离级别。