数据库死锁场景如何复现和解决?
|
freeflydom
2025年3月17日 10:32
本文热度 17
|
需了解死锁先看这一篇
死锁是如何被发现和解决的?这篇文章告诉你
一、死锁的产生原因
- 死锁发生在两个或多个事务相互等待对方释放锁,导致它们都无法继续执行的情况,形成死锁。
- 这种情况在并发高的系统中比较常见,尤其是在多个事务同时操作相同的数据时。
常见场景包括:
- 不同顺序访问资源:事务A先操作表1再操作表2,事务B先操作表2再操作表1。
- 索引缺失:全表扫描导致锁范围扩大,增加冲突概率。
- 长事务:事务长时间未提交,导致锁持有时间过长。
二、死锁场景复现(以MySQL为例)
- 复现死锁可以帮助理解问题发生的条件,从而更好地预防和解决。
1. 准备测试表和数据
CREATE TABLE account (
id INT PRIMARY KEY,
balance DECIMAL(10,2)
);
INSERT INTO account VALUES (1, 1000.00), (2, 2000.00);
2. 模拟两个事务交叉更新
事务A:先更新id=1,再更新id=2。
BEGIN;
UPDATE account SET balance = balance - 100 WHERE id = 1;
UPDATE account SET balance = balance + 100 WHERE id = 2;
COMMIT;
事务B:先更新id=2,再更新id=1。
BEGIN;
UPDATE account SET balance = balance - 200 WHERE id = 2;
UPDATE account SET balance = balance + 200 WHERE id = 1;
COMMIT;
3. 观察死锁
- 按顺序执行事务A和事务B的
UPDATE
语句。 - 事务A尝试更新id=2时,因事务B持有锁而等待。
- 事务B尝试更新id=1时,因事务A持有锁而等待。
- 数据库检测到死锁,自动回滚其中一个事务:
1205 - Lock wait timeout exceeded; try restarting transaction

三、解决死锁的核心方法
- 常见的解决策略包括设置合理的事务隔离级别、优化事务逻辑、使用超时机制、以及数据库自动检测和处理死锁。
1. 数据库自动处理
2. 代码层优化
- 统一资源访问顺序:所有事务按相同顺序操作表或记录。
UPDATE account SET ... WHERE id = 1;
UPDATE account SET ... WHERE id = 2;
- 减少事务粒度:避免长事务,尽快提交或回滚。
@Transactional(timeout = 5)
public void transfer() { ... }
- 使用乐观锁:通过版本号避免行锁竞争。
UPDATE account
SET balance = 900, version = version + 1
WHERE id = 1 AND version = 1;
3. 数据库配置调优
4. 重试机制
四、死锁分析工具
MySQL死锁日志:
- 查看
SHOW ENGINE INNODB STATUS
输出,分析TRANSACTION
和WAITING FOR THIS LOCK
部分。
pt-deadlock-logger(Percona工具):
实时监控死锁事件并记录。
pt-deadlock-logger --user=root --password=123456 --run-time=10
性能模式(Performance Schema):
MySQL 5.6+开启性能模式,监控锁等待事件。
SELECT * FROM performance_schema.events_transactions_current;

五、预防死锁
- 事务设计原则:
- 统一操作顺序:
所有业务逻辑按固定顺序访问资源(如按主键升序操作)。 - 监控与报警:
配置Prometheus监控死锁次数,超过阈值触发告警。 - 压力测试:
使用JMeter模拟并发事务,验证死锁概率。
六、总结
- 复现死锁:通过交叉更新不同顺序的资源,观察数据库自动回滚。
- 解决方案:
- 统一资源访问顺序,减少锁竞争。
- 优化索引和事务设计,降低死锁概率。
- 结合重试机制和数据库自动处理,提升系统容错性。
- 关键工具:数据库日志、性能分析工具、压力测试框架。
转自https://juejin.cn/post/7481837718718644274
该文章在 2025/3/17 10:33:31 编辑过