需了解死锁先看这一篇
一、死锁的产生原因
- 死锁发生在两个或多个事务相互等待对方释放锁,导致它们都无法继续执行的情况,形成死锁。
- 这种情况在并发高的系统中比较常见,尤其是在多个事务同时操作相同的数据时。
常见场景包括:
- 不同顺序访问资源:事务A先操作表1再操作表2,事务B先操作表2再操作表1。
- 索引缺失:全表扫描导致锁范围扩大,增加冲突概率。
- 长事务:事务长时间未提交,导致锁持有时间过长。
二、死锁场景复现(以MySQL为例)
- 复现死锁可以帮助理解问题发生的条件,从而更好地预防和解决。
1. 准备测试表和数据
sql
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。
sqlBEGIN; UPDATE account SET balance = balance - 100 WHERE id = 1; -- 等待事务B执行后再继续 UPDATE account SET balance = balance + 100 WHERE id = 2; COMMIT;
-
事务B:先更新id=2,再更新id=1。
sqlBEGIN; UPDATE account SET balance = balance - 200 WHERE id = 2; -- 等待事务A执行后再继续 UPDATE account SET balance = balance + 200 WHERE id = 1; COMMIT;
3. 观察死锁
-
按顺序执行事务A和事务B的
UPDATE
语句。 -
事务A尝试更新id=2时,因事务B持有锁而等待。
-
事务B尝试更新id=1时,因事务A持有锁而等待。
-
数据库检测到死锁,自动回滚其中一个事务:
sql1205 - Lock wait timeout exceeded; try restarting transaction
三、解决死锁的核心方法
- 常见的解决策略包括设置合理的事务隔离级别、优化事务逻辑、使用超时机制、以及数据库自动检测和处理死锁。
1. 数据库自动处理
-
死锁检测:数据库自动检测死锁并回滚代价较小的事务(如MySQL默认开启)。
-
查看死锁日志 (MySQL):
sqlSHOW ENGINE INNODB STATUS; -- 查看LATEST DETECTED DEADLOCK部分
2. 代码层优化
-
统一资源访问顺序 :所有事务按相同顺序操作表或记录。
sql-- 所有事务先更新id=1,再更新id=2 UPDATE account SET ... WHERE id = 1; UPDATE account SET ... WHERE id = 2;
-
减少事务粒度 :避免长事务,尽快提交或回滚。
java// 示例:使用Spring的@Transactional设置超时 @Transactional(timeout = 5) // 事务5秒未完成则回滚 public void transfer() { ... }
-
使用乐观锁 :通过版本号避免行锁竞争。
sqlUPDATE account SET balance = 900, version = version + 1 WHERE id = 1 AND version = 1;
3. 数据库配置调优
-
降低隔离级别 :从
REPEATABLE READ
改为READ COMMITTED
,减少锁冲突。sqlSET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-
索引优化 :为WHERE条件字段添加索引,缩小锁范围。
sqlALTER TABLE account ADD INDEX idx_id (id);
4. 重试机制
-
捕获死锁异常 :代码中捕获
DeadlockException
并重试。javaint retryCount = 0; while (retryCount < 3) { try { executeTransaction(); break; } catch (DeadlockException e) { retryCount++; Thread.sleep(100); // 等待后重试 } }
四、死锁分析工具
-
MySQL死锁日志:
- 查看
SHOW ENGINE INNODB STATUS
输出,分析TRANSACTION
和WAITING FOR THIS LOCK
部分。
- 查看
-
pt-deadlock-logger (Percona工具):
实时监控死锁事件并记录。
bashpt-deadlock-logger --user=root --password=123456 --run-time=10
-
性能模式(Performance Schema) :
MySQL 5.6+开启性能模式,监控锁等待事件。
sqlSELECT * FROM performance_schema.events_transactions_current;
五、预防死锁
- 事务设计原则 :
- 短事务优先,及时提交。
- 避免交叉更新多张表。
- 统一操作顺序 :
所有业务逻辑按固定顺序访问资源(如按主键升序操作)。 - 监控与报警 :
配置Prometheus监控死锁次数,超过阈值触发告警。 - 压力测试 :
使用JMeter模拟并发事务,验证死锁概率。
六、总结
- 复现死锁:通过交叉更新不同顺序的资源,观察数据库自动回滚。
- 解决方案 :
- 统一资源访问顺序,减少锁竞争。
- 优化索引和事务设计,降低死锁概率。
- 结合重试机制和数据库自动处理,提升系统容错性。
- 关键工具:数据库日志、性能分析工具、压力测试框架。