本文目录导读:

数据库死锁是指在并发事务中,两个或多个事务各自持有对方需要的锁,导致所有事务都无法继续执行,解决死锁问题通常需要从预防、检测、处理和监控四个层面入手,以下是系统性的解决方案:
预防死锁(设计层面)
从源头上减少死锁发生的概率,这是最推荐的方式。
-
统一访问顺序(资源排序法)
- 做法:让所有事务按照相同的顺序访问表和行,规定更新用户表和订单表时,必须先锁用户表,再锁订单表,这样就不会出现循环等待。
- 优点:彻底消除循环等待条件。
- 缺点:在复杂业务中很难维护全局统一的顺序。
-
缩短事务长度,减少持有锁的时间
- 做法:避免在事务中执行耗时操作(如远程RPC调用、复杂的批处理计算),将大事务拆分成多个小事务。
- 原理:锁持有的时间越长,冲突的概率越大。
-
选择合适的事务隔离级别
- 做法:在业务允许的情况下,尽量使用较低的隔离级别(如
READ COMMITTED代替SERIALIZABLE),以减少锁的范围(用行锁代替范围锁/间隙锁)。 - 注意:
SERIALIZABLE级别最容易产生死锁,因为它加锁最重。
- 做法:在业务允许的情况下,尽量使用较低的隔离级别(如
-
使用乐观锁代替悲观锁
- 做法:通过版本号(version)或时间戳实现,更新时检查数据是否被修改过,如果冲突则重试,而不是直接加锁等待。
- 适用场景:读多写少、并发冲突概率低的场景。
-
索引优化
- 做法:确保
WHERE子句和JOIN条件有合适的索引。 - 原理:没有索引会导致表锁(或锁住大量行),增加锁冲突的粒度,精确的索引可以让数据库只锁住必要的数据行。
- 做法:确保
死锁检测与自动处理(数据库机制)
现代数据库(如MySQL InnoDB、PostgreSQL、SQL Server)都内置了死锁检测机制。
-
开启死锁检测
- MySQL InnoDB:默认开启,数据库会定期检测等待图,发现循环等待时,强制回滚一个代价较小的事务(牺牲者)。
- 设置:
innodb_deadlock_detect = ON(默认)。 - 权衡:在高并发场景下,死锁检测本身会消耗CPU资源,对于高并发业务,如果确认不会产生死锁,也可以关闭检测(
innodb_deadlock_detect = OFF),并依赖锁等待超时。
-
设置锁等待超时
- 做法:设定一个最大等待时间(
innodb_lock_wait_timeout,默认50秒),超时后事务自动回滚。 - 缺点:超时是事后处理,用户会感知到长时间的等待,效果不如死锁检测好。
- 做法:设定一个最大等待时间(
应用程序层面的处理(容错与重试)
这是后端开发中最关键的防线,即使预防做得再好,死锁仍可能发生。
-
捕获死锁错误
- 做法:在代码中捕获数据库返回的特定错误码。
- MySQL:错误码
1213(ER_LOCK_DEADLOCK)。 - PostgreSQL:错误码
40P01(T_DEADLOCK_DETECTED)。 - SQL Server:错误号
1205。
- MySQL:错误码
- 做法:在代码中捕获数据库返回的特定错误码。
-
实现重试机制
- 做法:当捕获到死锁错误时,不要直接报错给用户,而是等待一小段时间(如100-500ms)后,自动重新执行整个被打断的事务。
- 代码示例(伪代码):
MAX_RETRIES = 3 retries = 0 while retries < MAX_RETRIES: try: # 开启事务 # 执行SQL操作 # 提交事务 break # 成功则跳出循环 except DeadlockError as e: retries += 1 print(f"死锁发生,重试第{retries}次") time.sleep(0.2 * retries) # 指数退避等待 if retries >= MAX_RETRIES: raise e # 重试耗尽,抛出异常
诊断与排查(事后分析)
当问题已经频繁出现时,需要通过日志和工具找出“带锁的SQL”。
-
开启死锁日志
- MySQL:
SHOW ENGINE INNODB STATUS\G,查看LATEST DETECTED DEADLOCK部分。 - SQL Server:使用系统监控或扩展事件跟踪
deadlock_graph。 - 分析重点:看两个事务分别持有的是什么锁、等待的是什么锁、涉及哪张表和哪条索引。
- MySQL:
-
检查慢查询
死锁经常伴随着慢查询,一条SQL执行时间过长,导致它持锁时间过长,容易引发其他事务等待。
-
分析ER图和SQL执行计划
- 通过
EXPLAIN分析是否有全表扫描或索引失效的情况,这也是导致锁范围扩大的常见原因。
- 通过
最佳实践流程
- 开发阶段:设计时遵循统一访问顺序,保持事务短小精悍,索引设计合理。
- 编程阶段:所有数据库操作都加上重试机制(捕获死锁异常后重试)。
- 运维阶段:监控
innodb_lock_wait_timeout和死锁日志,持续优化SQL和索引。 - 高级策略:对于极高并发且死锁频繁的场景(如秒杀),考虑使用乐观锁(CAS)或排队系统(消息队列 + 异步处理),而不是依赖数据库行锁。
通过“预防为主、检测为辅、重试兜底、监控优化”的四位一体策略,可以有效解决大部分数据库死锁问题。