数据库死锁

2026-07-18wei👁 12 阅读3 分钟阅读📝 1389 字💬 0 评论
数据库死锁

数据库死锁

1. 什么是死锁?(经典转账场景举例)

假设数据库里有两条记录:A账户(100元) 和 B账户(100元)。
事务1(小明) 要做:给A减10元,给B加10元。
事务2(小红) 要做:给B减10元,给A加10元。

时间线执行流程:

  1. 事务1 拿到 A账户 的锁(准备扣A的钱)。
  2. 事务2 拿到 B账户 的锁(准备扣B的钱)。
  3. 事务1 想拿 B账户 的锁,发现被事务2占着,于是等待。
  4. 事务2 想拿 A账户 的锁,发现被事务1占着,于是等待。

结果:事务1等事务2释放B,事务2等事务1释放A。双方谁也不松手,永远僵持,这就是死锁(Deadlock)。

2. 死锁产生的4个必要条件(缺一不可)

数据库死锁必须同时满足以下4个条件,只要打破任意一个,死锁就不会产生:

  1. 互斥:同一时间,一把锁只能被一个事务持有(A账户不能同时被两个人修改)。
  2. 占有并等待:事务1拿着A锁,还在等着拿B锁,不释放已持有的锁。
  3. 不可抢占:事务1不主动释放A锁,事务2无法强行抢走锁资源。
  4. 循环等待:事务1等事务2,事务2等事务1,形成资源等待环路。

3. 数据库如何处理死锁?

以MySQL、Oracle为例,数据库不会放任事务无限阻塞,内置死锁检测机制:

  1. 数据库会评估两个事务执行代价(修改数据量、耗时等),选出牺牲品(Victim)
  2. 强制回滚(Rollback)牺牲品事务,释放它占用的全部锁资源;
  3. 剩余事务获取全部所需锁,正常执行完成。

被回滚的事务会抛出报错:
Deadlock found when trying to get lock; try restarting transaction

核心结论:死锁不会造成数据库崩溃,是数据库主动“劝架”,业务侧只需重试事务即可。

4. 死锁 VS 锁等待(Lock Wait)区别

  • 锁等待:单方向排队等待,事务2等待事务1释放锁,事务1执行完毕一定会释放锁,属于正常并发排队现象。
  • 死锁:双向循环互相等待,双方永远无法主动释放锁,必须依靠数据库强制回滚其中一个事务才能解除阻塞。

5. 开发中如何预防死锁?(优先预防,优于事后处理)

数据库虽能自动处理死锁,但频繁死锁会大幅降低数据库并发性能,推荐以下预防方案:

  1. 固定资源访问顺序:所有事务统一按「先A后B」固定顺序更新数据,杜绝交叉等待环路。
  2. 缩短事务执行时长:事务内部禁止耗时网络请求、大量计算逻辑,锁持有时间越短,锁冲突概率越低。
  3. 使用更低隔离级别:使用「读已提交(Read Committed)」替代默认「可重复读(Repeatable Read)」,缩小间隙锁锁定范围。
  4. 添加合理业务索引:更新、修改语句必须命中索引;无索引会触发表锁,极大提升死锁发生概率。

6. 对比总结:内存溢出(OOM) vs 数据库死锁(Deadlock)

对比维度 内存溢出 (OOM) 数据库死锁 (Deadlock)
本质 内存资源耗尽,资源不足 锁资源互相抢占,循环等待
发生位置 应用服务器内存 / JVM堆 InnoDB等数据库存储引擎
业务后果 进程崩溃,OOM杀手终止进程 单个事务回滚,应用抛出异常,服务进程正常存活
解决方案 扩容内存、修复内存泄漏、优化代码 事务重试、统一SQL更新顺序、补充索引

7. 面试/实战小技巧

Spring框架捕获死锁异常:DeadlockLoserDataAccessException
最佳实践:不修改核心业务逻辑,增加事务重试机制(例如重试3次),数据库已完成锁释放,重试通常能执行成功。

wei
技术博客作者
1389 字 · 0 评论
2026-07-18

评论 (0)

暂无评论,来写第一条吧

登录后发表评论

分类

友链

关于本站

用 SpringBoot + Nuxt3 搭建的个人技术博客,记录编程之路的思考与收获。

© 2026 好啵博客 · Built with ❤️ & SpringBoot + Nuxt3