一、什么是死锁(Deadlock)
定义:
死锁是指两个或多个事务在执行过程中,互相占用资源且等待对方释放,导致事务都无法继续执行的状态。
简单例子:
事务A | 事务B |
---|---|
UPDATE t1 SET ... WHERE id=1; |
UPDATE t1 SET ... WHERE id=2; |
(锁住 id=1) | (锁住 id=2) |
UPDATE t1 SET ... WHERE id=2; ← 被事务B锁住 |
UPDATE t1 SET ... WHERE id=1; ← 被事务A锁住 |
🔒 这样双方互相等待,InnoDB 检测到死锁后,会自动回滚其中一个事务(通常是锁占用资源少的),释放资源。
二、InnoDB 死锁原理
InnoDB 是 行级锁 + MVCC 的引擎,会使用不同类型的锁:
锁类型 | 说明 |
---|---|
共享锁 (S Lock) | 允许读取,不允许写入 |
排他锁 (X Lock) | 允许修改,不允许其他事务读/写 |
意向锁 (IS/IX Lock) | 用于协调表锁和行锁 |
间隙锁 (Gap Lock) | 用于防止幻读(在可重复读隔离级别下) |
死锁的本质是:
事务之间的锁依赖图中形成了环。
三、死锁检测与分析
1️⃣ 自动检测
MySQL 会自动检测死锁并回滚其中一个事务,返回错误:
2️⃣ 查看死锁日志
执行:
在输出中查找:
你能看到:
-
哪两个事务冲突;
-
哪个 SQL 语句导致死锁;
-
哪个事务被回滚。
👉 生产场景下,死锁分析一般就是从这个日志开始。
四、常见死锁场景
场景 | 说明 |
---|---|
1. 更新相同表的不同记录 | 事务A更新id=1再更新id=2,事务B反过来更新id=2再更新id=1。顺序不一致引发死锁。 |
2. 唯一索引冲突 + 插入 | 两个事务插入相同唯一键的记录,InnoDB 会隐式加锁。 |
3. 间隙锁冲突 | 可重复读隔离级别下,范围查询 + 插入导致 gap lock 竞争。 |
4. 外键约束 | 更新/删除父表、子表数据顺序不一致时,会触发隐式锁定。 |
5. 大事务 / 批量更新 | 一个事务锁住大量行,另一个事务锁少量行,可能产生循环等待。 |
五、死锁的危害
-
❌ 部分事务会被强制回滚(数据未提交)。
-
⚠️ 系统性能下降(大量事务等待 / 回滚重试)。
-
🧨 高频死锁会导致应用报错、事务重试压力增大。
-
🪣 如果应用层没有正确处理异常(例如直接吞掉异常),可能出现业务逻辑不一致。
六、死锁的优化与避免策略
✅ 1. 保证加锁顺序一致
所有业务中操作相同表时,应固定访问顺序。
✅ 2. 尽量缩短事务执行时间
-
事务只包含必要的 SQL;
-
不要在事务中长时间等待外部资源(RPC、IO);
-
提交要及时。
✅ 3. 为查询条件加合适索引
防止锁范围扩大(否则会锁整表或大量记录)。
✅ 4. 减少范围锁(Gap Lock)
-
使用 READ COMMITTED 隔离级别(可减少间隙锁);
-
或尽量使用主键/唯一索引精确查询。
✅ 5. 拆分大事务
批量更新、删除操作可以拆分为小批量提交。
✅ 6. 避免并发更新相同行
可以加版本号、状态字段等乐观锁机制(应用层控制)。
✅ 7. 捕获并重试
对死锁异常进行捕获与重试(常用于幂等事务逻辑):
七、面试总结答案(标准化表达)
面试答法:
MySQL 死锁是多个事务之间互相持有并等待对方锁资源而无法继续执行的现象。
InnoDB 会自动检测死锁并回滚其中一个事务。
常见死锁场景包括不同顺序更新同一表、间隙锁冲突、外键约束等。
处理上我们可以通过:
保证加锁顺序一致;
缩短事务时间;
为条件加索引;
使用合适隔离级别(READ COMMITTED);
拆分大事务;
捕获异常并重试;
通过
SHOW ENGINE INNODB STATUS
分析死锁日志。死锁会导致事务回滚和性能下降,需要重点在设计阶段优化。