线上某服务时不时报出如下异常(大约一天二十多次):“Deadlock found when trying to get lock;”。
1 死锁是怎么被发现的?
1.1 死锁成因&&检测方法
1.2 wait-for graph原理
1.2.1 锁与索引的关系
id: bigint | token: varchar(30) | message: varchar(4096) |
1)delete from msg where id=2;
2)delete from msg where token=’ cvs’;
3)delete from msg where message=订单号是多少’;
1.2.2 锁与隔离级别的关系
1)未提交读(Read uncommitted);
2)已提交读(Read committed(RC));
3)可重复读(Repeatable read(RR));
4)可串行化(Serializable)。
我们较常使用的是RC和RR。
RC级别下尽管加了行锁,但还是避免不了幻读。
RR隔离级别可以避免幻读发生,怎么实现?当然需要借助于锁了!
gap锁(间隙锁)。
3 死锁成因
3.1不同表相同记录行锁冲突
3.2相同表记录行锁冲突
3.3不同索引锁冲突
3.4 gap锁冲突
4 如何尽可能避免死锁
1)以固定的顺序访问表和行。比如对第2节两个job批量更新的情形,简单方法是对id列表先排序,后执行,这样就避免了交叉等待锁的情形;又比如对于3.1节的情形,将两个事务的sql顺序调整为一致,也能避免死锁。
2)大事务拆小。大事务更倾向于死锁,如果业务允许,将大事务拆小。
3)在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率。
4)降低隔离级别。如果业务允许,将隔离级别调低也是较好的选择,比如将隔离级别从RR调整为RC,可以避免掉很多因为gap锁造成的死锁。
5)为表添加合理的索引。可以看到如果不走索引将会为表的每一行记录添加上锁,死锁的概率大大增大。
5 如何定位死锁成因
1)通过应用业务日志定位到问题代码,找到相应的事务对应的sql;
start tran 1 deleteHeartCheckDOByToken 2 updateSessionUser ... commit
2)确定数据库隔离级别。
3)找DBA执行下show InnoDB STATUS看看最近死锁的日志。
这不就是图10描述的死锁嘛!
start tran 1 updateSessionUser 2 deleteHeartCheckDOByToken ... commit