问题描述:
检查程序运行日志的时候, 发现了很多DB死锁, 我这边程序中使用的是悲观锁, 因为考虑到不想让调用端重试。
死锁原因:
有多个请求同时希望insert表, 程序中逻辑如下:
select for update where uid = ?
if [obj ]not exist
insert
else
update
死锁原因就是 select for update 如果记录不存在mysql会先加一个意向锁, 当多个请求(多个线程) 同时加了意向锁之后(意向锁之间可兼容), 第一个线程尝试insert的时候会尝试加排他锁, 这时排他锁会被第二个线程的pending住, 此时第二个线程尝试插入的时候, 也会尝试加排他锁, 这个时候就会出现deadlock的情况
加锁分析
- where子句没有满足条件的记录,而对于不存在的记录 并且在RR级别下,加锁类型为gap lock,gap lock之间是兼容的,所以两个事务都能成功执行;关于gap lock可以参考文章加锁分析。这里的gap范围是索引a列(3,5)的范围。
- insert时,其加锁过程为先在插入间隙上获取插入意向锁,插入数据后再获取插入行上的排它锁。又插入意向锁与gap lock和 Next-key lock冲突,即一个事务想要获取插入意向锁,如果有其他事务已经加了gap lock或 Next-key lock,则会阻塞。
- 场景中两个事务都持有gap lock,然后又申请插入意向锁,此时都被阻塞,循环等待造成死锁。
锁兼容矩阵:
锁兼容矩阵
死锁解决
方案如下几种选择:
- 不采用事务包装这部分逻辑,本文实际业务场景中可以不需要事务,所以直接取消事务包装即可,采用insert ON DUPLICATE KEY UPDATE的方式
- 调整事务隔离级别为read commit,RC级别不会产生gap lock
- 利用分布式锁
- sql改写
select
if [obj] not exist
insert
else
select for update
update