什么是数据库死锁,怎么解决?

一则或许对你有用的小广告

欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于 Spring AI + Spring Boot3.x + JDK 21...点击查看;
  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot3.x + JDK 17...点击查看项目介绍; 演示链接: http://116.62.199.48:7070/;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/

面试考察点

  1. 基础概念掌握:面试官不仅仅是想知道你听过 "死锁" 这个词,更是想考察你是否真正理解死锁产生的四个必要条件(互斥、占有并等待、不可抢占、循环等待),以及为什么在数据库中会发生死锁。

  2. 实际场景应对能力:考察你是否在项目中遇到过死锁问题,能否快速定位死锁的原因,以及是否有实际解决问题的经验。这反映了你对生产环境的掌控能力。

  3. 预防与解决的系统性思维:面试官想了解你是否具备从多个维度(数据库配置、SQL 编写规范、业务逻辑设计、监控预警)来预防和解决死锁的能力,而不仅仅是知道几个零散的知识点。

核心答案

数据库死锁是指两个或多个事务在执行过程中,因争夺资源而造成的一种互相等待的现象。当死锁发生时,这些事务都无法继续执行,除非有外力介入打破这种循环等待。

解决方案可以从三个层面来处理:

层面方法说明
数据库层面超时机制、死锁检测innodb_lock_wait_timeout 设置等待超时;innodb_deadlock_detect 开启死锁检测(默认开启)
SQL 编写层面统一访问顺序、减小事务粒度所有事务按相同顺序访问表和行;大事务拆分为小事务
业务设计层面降低隔离级别、优化索引使用 RC 隔离级别减少锁范围;建立合适索引避免全表扫描

一句话总结:死锁无法完全避免,但通过统一访问顺序、减小锁粒度、优化索引等手段可以大幅降低发生概率,同时配合数据库的死锁检测和超时机制来兜底。

深度解析

一、死锁产生的四个必要条件

上图展示了死锁产生的四个必要条件。只有当这四个条件同时满足时,死锁才会发生。因此,预防死锁的核心思路就是破坏其中一个或多个条件

  • 互斥条件:资源在同一时间只能被一个事务占用。数据库锁天然满足这个条件,无法破坏。

  • 占有并等待条件:事务已经持有了某些资源,同时还在等待获取其他资源。可以通过"一次性申请所有资源"来破坏,但实现成本较高。

  • 不可抢占条件:事务已获得的资源不能被强制剥夺。数据库可以通过超时或死锁检测机制来"抢占"(回滚其中一个事务)。

  • 循环等待条件:事务之间形成循环等待关系。可以通过"统一访问顺序"来破坏,这是最实用的方法。

二、典型死锁场景演示

上图展示了最常见的死锁场景:两个事务以不同顺序访问相同的资源

具体流程如下:

  • 步骤 ①:事务 A 首先锁定 id=1 的行,成功获得锁 A。

  • 步骤 ②:事务 B 同时锁定 id=2 的行,成功获得锁 B。

  • 步骤 ③:事务 A 尝试锁定 id=2 的行,但该行已被事务 B 锁定,因此进入等待状态。

  • 步骤 ④:事务 B 尝试锁定 id=1 的行,但该行已被事务 A 锁定,也进入等待状态。

此时形成了循环等待:A 等待 B 释放锁,B 等待 A 释放锁,谁都无法继续,死锁产生。

三、解决方案详解

1. 统一访问顺序(破坏循环等待条件)

-- ❌ 错误示例:不同事务以不同顺序访问
-- 事务 A:先 1 后 2
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

-- 事务 B:先 2 后 1
UPDATE accounts SET balance = balance - 100 WHERE id = 2;
UPDATE accounts SET balance = balance + 100 WHERE id = 1;

-- ✅ 正确示例:统一按 id 升序访问
-- 事务 A 和 B 都按相同顺序
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

核心原则:所有涉及多表或多行操作的事务,都按照相同的顺序(如按主键升序)访问资源,这样就不会形成循环等待。

2. 减小事务粒度(减少锁持有时间)

-- ❌ 错误示例:大事务,锁持有时间长
BEGIN;
-- 查询数据
SELECT * FROM orders WHERE user_id = 100;
-- 调用外部接口(耗时操作)
CALL external_service();
-- 更新数据
UPDATE orders SET status = 'PAID' WHERE user_id = 100;
COMMIT;

-- ✅ 正确示例:拆分事务,减少锁持有时间
-- 事务 1:只负责查询
SELECT * FROM orders WHERE user_id = 100;

-- 事务 2:只负责更新(锁持有时间短)
BEGIN;
UPDATE orders SET status = 'PAID' WHERE user_id = 100;
COMMIT;

核心原则:将大事务拆分为多个小事务,只在必要时获取锁,并尽快释放。避免在事务中执行耗时操作(如网络请求、复杂计算)。

3. 优化索引(减少锁范围)

-- ❌ 错误示例:无索引导致全表扫描,锁定所有行
-- 假设 status 字段没有索引
UPDATE orders SET status = 'CANCELLED' WHERE status = 'PENDING';

-- ✅ 正确示例:建立索引,只锁定符合条件的行
CREATE INDEX idx_status ON orders(status);
UPDATE orders SET status = 'CANCELLED' WHERE status = 'PENDING';

核心原则:没有合适的索引时,数据库可能进行全表扫描并锁定所有扫描过的行(取决于隔离级别),极大增加死锁概率。建立合适的索引可以让数据库只锁定目标行

4. 数据库层面的配置

-- 查看当前死锁检测配置
SHOW VARIABLES LIKE 'innodb_deadlock_detect';

-- 查看锁等待超时时间(默认 50 秒)
SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';

-- 调整超时时间(根据业务需求)
SET innodb_lock_wait_timeout = 30;
参数默认值说明
innodb_deadlock_detectON开启死锁检测,发现死锁后自动回滚其中一个事务
innodb_lock_wait_timeout50锁等待超时时间(秒),超时后事务自动回滚

核心原则:生产环境建议开启死锁检测(默认开启),并设置合理的超时时间。死锁检测能主动发现并解决死锁,超时机制作为兜底。

四、死锁排查与监控

-- 查看最近的死锁信息
SHOW ENGINE INNODB STATUS;

-- 在输出中查找 "LATEST DETECTED DEADLOCK" 部分
-- 可以看到:
-- 1. 哪些事务参与了死锁
-- 2. 每个事务持有哪些锁
-- 3. 每个事务在等待哪些锁
-- 4. MySQL 选择回滚了哪个事务

监控建议

  • 定期检查 SHOW ENGINE INNODB STATUS 输出中的死锁信息
  • 开启慢查询日志和错误日志,记录死锁发生的时间点
  • 使用监控工具(如 Prometheus + Grafana)监控 Innodb_deadlocks 指标
  • 建立死锁告警机制,及时发现异常

面试高频追问

  1. InnoDB 是如何检测死锁的?

    InnoDB 使用等待图算法:为每个事务创建节点,如果事务 A 等待事务 B 的锁,就画一条 A → B 的边。定期检测图中是否存在环,存在环则说明有死锁。

  2. 发生死锁后,MySQL 如何选择回滚哪个事务?

    MySQL 会选择回滚代价最小的事务,通常是根据事务已经修改的行数、锁定的资源数量等因素来判断。

  3. RR 和 RC 隔离级别下,死锁有什么区别?

    RR(可重复读)使用 Gap LockNext-Key Lock,锁定范围更大,死锁概率更高;RC(读已提交)只使用 Record Lock,锁定范围小,死锁概率相对较低。

常见面试变体

  • "你项目中遇到过死锁吗?是怎么排查和解决的?"
  • "如何设计系统来预防数据库死锁?"
  • "MySQL InnoDB 的锁机制是什么?什么情况下会产生死锁?"
  • "Gap Lock 是什么?它和死锁有什么关系?"

记忆口诀

死锁四个条件:互斥、占有等、不可抢、循环等(四个必须同时满足)

解决方案:统一顺序破循环,小事务减持有,加索引缩范围,开检测做兜底

排查口诀:SHOW ENGINE STATUS 看死锁,哪个事务哪把锁,等待图谱全明了

总结

数据库死锁是多事务并发时资源竞争导致的循环等待现象。核心解决方案是统一访问顺序(破坏循环等待)、减小事务粒度(减少锁持有时间)、优化索引(减少锁范围),同时配合数据库的死锁检测和超时机制兜底。生产环境中应建立死锁监控告警,及时发现并处理问题。