一、常见死锁场景
1. 不同顺序的锁获取
-
场景:事务A按顺序更新
行1 → 行2
,事务B按行2 → 行1
顺序更新。 -
原因:双方各持有一把锁,同时请求对方持有的锁,形成循环等待。
2. 索引缺失导致锁升级
-
场景:更新操作未命中索引,导致全表扫描,锁住所有记录(甚至间隙锁)。
-
原因:无索引时,行锁可能升级为表锁,增大死锁概率。
3. 间隙锁(Gap Lock)冲突
-
场景:在可重复读(RR)隔离级别下,事务A删除某范围数据,事务B尝试插入相同范围的数据。
-
原因:间隙锁阻塞插入操作,导致互相等待。
4. 唯一键冲突
-
场景:高并发下插入相同唯一键数据,事务A和事务B同时尝试插入,触发唯一约束冲突。
-
原因:回滚时释放锁的顺序可能引发死锁。
5. 混合操作(SELECT ... FOR UPDATE + UPDATE)
-
场景:事务A先
SELECT ... FOR UPDATE
锁定行1,再更新行2;事务B反向操作。 -
原因:显式锁与隐式锁交叉请求。
二、解决方案
1. 统一锁顺序
-
操作:确保所有事务按相同顺序访问资源(如统一按主键升序更新)。
-
示例:事务A和事务B均按
行1 → 行2
顺序更新。
2. 优化索引设计
-
操作:为高频查询/更新字段添加索引,避免全表扫描。
-
示例:对
WHERE
或UPDATE
条件字段建立索引,缩小锁范围。
3. 降低事务粒度
-
操作:减少事务内操作,尽快提交释放锁。
-
示例:避免在事务中执行耗时操作(如外部API调用)。
4. 设置合理隔离级别
-
操作:使用
READ COMMITTED
替代REPEATABLE READ
,减少间隙锁的使用。 -
注意:需权衡一致性与并发性能。
5. 重试机制
-
操作:捕获死锁异常(错误码
1213
),等待随机时间后重试事务。 -
代码示例(伪代码):
python
复制
try:execute_transaction() except DeadlockError:wait(random_time)retry()
6. 避免长事务
-
操作:监控并拆分执行时间过长的事务,减少锁持有时间。
-
工具:通过
SHOW PROCESSLIST
或INFORMATION_SCHEMA.INNODB_TRX
监控事务。
7. 使用乐观锁
-
操作:通过版本号或时间戳实现无锁更新。
-
示例:
UPDATE table SET col = new_val, version = version + 1 WHERE id = 1 AND version = old_version;
8. 分析死锁日志
-
操作:通过
SHOW ENGINE INNODB STATUS
查看死锁日志,定位冲突SQL。 -
关键信息:关注
LATEST DETECTED DEADLOCK
部分,分析锁类型(行锁、间隙锁)和事务操作路径。
三、案例分析
案例1:不同顺序更新导致的死锁
-
事务A:
BEGIN; UPDATE users SET score = 100 WHERE id = 1; UPDATE users SET score = 200 WHERE id = 2; COMMIT;
-
事务B:
BEGIN; UPDATE users SET score = 300 WHERE id = 2; UPDATE users SET score = 400 WHERE id = 1; COMMIT;
-
解决:统一按主键升序更新(先更新
id=1
再id=2
)。
案例2:唯一键插入死锁
-
场景:两个事务同时插入相同唯一键数据。
-
解决:使用
INSERT ... ON DUPLICATE KEY UPDATE
或先检查后插入(需捕获异常)。
四、总结
场景 | 解决策略 |
---|---|
不同顺序锁竞争 | 统一资源访问顺序 |
无索引导致锁升级 | 优化索引设计 |
间隙锁冲突 | 降低隔离级别或避免范围操作 |
长事务阻塞 | 拆分事务,快速提交 |
高并发写入冲突 | 乐观锁或重试机制 |
通过合理设计事务、优化索引、分析日志及结合重试机制,可有效减少死锁发生概率。
更加详细案例参考:
Mysql死锁场景案例及解决方案-CSDN博客