数据库在什么情况下会发生数据库死锁
1. 什么是死锁
死锁(Deadlock) 是指两个或多个事务在执行过程中,互相持有对方需要的锁,并且都在等待对方释放锁,从而导致它们都无法继续执行。
简单来说:
你等我释放锁,我等你释放锁,结果谁也不释放 → 永远卡住。
2. 死锁发生的必要条件
死锁通常满足以下四个条件(同时成立才会发生):
- 互斥条件:锁资源一次只能被一个事务占用。
- 请求与保持条件:事务已经持有锁,同时又请求新的锁。
- 不可剥夺条件:锁不能被强制释放,只能由持有它的事务主动释放。
- 循环等待条件:事务之间形成一个循环等待锁的关系。
3. MySQL InnoDB 中常见死锁场景
场景 1:不同事务锁定资源顺序不同
-- 事务A
BEGIN;
UPDATE table1 SET col = 1 WHERE id = 1; -- 锁住 table1 的 id=1
UPDATE table2 SET col = 1 WHERE id = 2; -- 等待 table2 的 id=2-- 事务B
BEGIN;
UPDATE table2 SET col = 1 WHERE id = 2; -- 锁住 table2 的 id=2
UPDATE table1 SET col = 1 WHERE id = 1; -- 等待 table1 的 id=1
场景 2:间隙锁(Gap Lock)导致死锁
在 REPEATABLE READ 隔离级别下,InnoDB 会使用 间隙锁 来防止幻读。
如果两个事务在范围查询时加了间隙锁,并且互相等待对方释放,就可能死锁。
-- 事务A
SELECT * FROM orders WHERE amount BETWEEN 100 AND 200 FOR UPDATE;-- 事务B
SELECT * FROM orders WHERE amount BETWEEN 150 AND 250 FOR UPDATE;
两个事务锁定的范围有重叠,可能导致互相等待。
场景 3:外键约束导致死锁
当更新或删除带有外键约束的表时,InnoDB 会对父表和子表加锁,如果两个事务操作顺序不同,也可能死锁。
场景 4:索引选择不当
如果查询条件没有用到索引,InnoDB 会锁住更多行(甚至全表锁),增加死锁概率。
4. MySQL 如何处理死锁
- InnoDB 有死锁检测机制(
innodb_deadlock_detect默认开启)。 - 一旦检测到死锁,InnoDB 会回滚其中一个事务,让另一个事务继续执行。
- 回滚的事务会收到错误:
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
5. 避免死锁的建议
- 统一加锁顺序:所有事务访问表和行的顺序保持一致。
- 减少锁持有时间:尽量缩短事务执行时间,及时提交。
- 合理使用索引:避免全表扫描加锁。
- 减少范围锁:能用精确匹配就不要用范围查询。
- 分解大事务:将大事务拆成多个小事务,降低锁冲突概率。
✅ 关键记忆:
- 死锁本质是循环等待锁资源。
- 常见原因:加锁顺序不一致、间隙锁冲突、外键锁冲突、索引不当导致锁范围过大。
- InnoDB 会自动检测并回滚一个事务,但最好在设计时避免死锁。
MySQL 发生死锁后的处理方法,并给你一个实际案例,让你清楚怎么发现、分析和解决死锁问题
1. 死锁发生时 MySQL 的处理机制
- InnoDB 存储引擎会自动检测死锁(
innodb_deadlock_detect=ON默认开启)。 - 一旦检测到死锁,InnoDB 会主动回滚其中一个事务(通常是回滚代价最小的那个),让另一个事务继续执行。
- 被回滚的事务会收到错误:
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
2. 死锁处理步骤
步骤 1:捕获死锁错误
应用层要捕获 ERROR 1213 错误,并重试事务:
try {// 执行事务逻辑
} catch (SQLException e) {if (e.getErrorCode() == 1213) {// 死锁,重试事务}
}
步骤 2:分析死锁原因
MySQL 提供了命令查看最近一次死锁信息:
SHOW ENGINE INNODB STATUS\G
在输出中找到 LATEST DETECTED DEADLOCK 部分,可以看到:
- 哪些事务参与了死锁
- 每个事务持有的锁
- 每个事务等待的锁
- 最终哪个事务被回滚
步骤 3:优化 SQL / 事务逻辑
常见优化方法:
- 统一加锁顺序(避免循环等待)
- 减少锁范围(使用合适的索引,避免全表锁)
- 缩短事务时间(减少锁持有时间)
- 分解大事务(降低锁冲突概率)
- 避免不必要的范围锁(减少间隙锁的使用)
3. 死锁案例
表结构
CREATE TABLE account (id INT PRIMARY KEY,balance DECIMAL(10,2)
) ENGINE=InnoDB;INSERT INTO account VALUES (1, 1000), (2, 1000);
事务执行过程
-- 事务A
BEGIN;
UPDATE account SET balance = balance - 100 WHERE id = 1; -- 锁住 id=1
UPDATE account SET balance = balance + 100 WHERE id = 2; -- 等待 id=2-- 事务B
BEGIN;
UPDATE account SET balance = balance - 100 WHERE id = 2; -- 锁住 id=2
UPDATE account SET balance = balance + 100 WHERE id = 1; -- 等待 id=1
此时:
- 事务A 持有
id=1锁,等待id=2 - 事务B 持有
id=2锁,等待id=1 - 循环等待 → 死锁
死锁检测结果
执行:
SHOW ENGINE INNODB STATUS\G
可能看到:
LATEST DETECTED DEADLOCK
------------------------
*** (1) TRANSACTION:
TRANSACTION 12345, ACTIVE 5 sec updating
...
*** (2) TRANSACTION:
TRANSACTION 12346, ACTIVE 5 sec updating
...
*** WE ROLL BACK TRANSACTION (2)
表示事务 12346 被回滚。
解决方法
- 统一加锁顺序:
- 所有事务先更新
id=1再更新id=2,避免循环等待。
- 所有事务先更新
- 应用层重试:
- 捕获
ERROR 1213,重新执行事务。
- 捕获
✅ 总结:
- 发生死锁时:InnoDB 会自动回滚一个事务 → 应用层要捕获错误并重试。
- 分析死锁:用
SHOW ENGINE INNODB STATUS\G查看死锁详情。 - 避免死锁:统一加锁顺序、减少锁范围、缩短事务时间、分解大事务。
