# 简述
现在大多项目都是使用RC事务隔离级别, 同时将binlog format设置为row. RR下死锁相对较多, 且因为next-key lock, 容易导致死锁, 而对RC下的死锁, 认识得比较浅显, 由于最近正好碰到一些场景, 故将其整理, 一起交流学习.
# 实战场景
所有场景均在read committed事务隔离级别下
# 表结构与数据
# 与其他专题演示表结构一样
# 表t, id为主键, c为索引(normal), d什么都不是
# 表数据: (0,0,0),(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25);
# 场景一, update顺序导致的死锁
| session A | session B | |
|---|---|---|
| T1 | begin; update t set d=100 where id=5; (ok) | |
| T2 | begin; update t set d=200 where d=10; (ok) | |
| T3 | update t set d=200 where d=10; (block) | |
| T4 | update t set d=100 where id=5; (Deadlock found...) |
场景一分析
本例由于两个事务都要更新两行数据, 但由于update顺序问题, 导致在T3,T4时刻互相锁住, T3被T2锁住, T4被T1锁住, 最后死锁检测报错.
场景一解决方案
- 保证执行顺序. 如果是由于同一个逻辑导致的死锁, 那保证顺序一般没有问题, 如果执行逻辑不在同一处, 需要看业务逻辑是否能保证顺序.
- 锁住执行语句块, 可以使用分布式锁之类方式保证.
# 场景二, 普通索引与主键索引相互等导致死锁
| session A | session B | |
|---|---|---|
| T1 | begin; update t set d=100 where id=5; (ok) | |
| T2 | update t set d=200 where c=5; (block) | |
| T3 | update t set d=300 where c=5; (ok) | T2时刻的update从block变为Deadlock found... |
场景二分析
- T1, update锁住id=5的主键索引.
- T2, 查询条件c=5为索引查询, 会根据索引c找到对应的id, 同时将两者锁住, 但是由于session A已经锁住id=5, 因此只锁住c=5的索引, 等待获取id=5的主键锁(被阻塞).
- T3, update索引c=5的行, 由于检测到session B已经锁住c=5的索引, 因此发现session A和session B死锁, 致使session B直接抛错不执行, session A语句执行成功.
场景二解决方案
尽量保证使用update语句都是基于主键查询.