select for update 并发insert死锁问题

问题描述:

检查程序运行日志的时候, 发现了很多DB死锁, 我这边程序中使用的是悲观锁, 因为考虑到不想让调用端重试。

死锁原因:

有多个请求同时希望insert表, 程序中逻辑如下:

select for update where uid = ?

if [obj ]not exist

insert

else

update

死锁原因就是 select for update 如果记录不存在mysql会先加一个意向锁, 当多个请求(多个线程) 同时加了意向锁之后(意向锁之间可兼容), 第一个线程尝试insert的时候会尝试加排他锁, 这时排他锁会被第二个线程的pending住, 此时第二个线程尝试插入的时候, 也会尝试加排他锁, 这个时候就会出现deadlock的情况

加锁分析

  1. where子句没有满足条件的记录,而对于不存在的记录 并且在RR级别下,加锁类型为gap lock,gap lock之间是兼容的,所以两个事务都能成功执行;关于gap lock可以参考文章加锁分析。这里的gap范围是索引a列(3,5)的范围。
  2. insert时,其加锁过程为先在插入间隙上获取插入意向锁,插入数据后再获取插入行上的排它锁。又插入意向锁与gap lock和 Next-key lock冲突,即一个事务想要获取插入意向锁,如果有其他事务已经加了gap lock或 Next-key lock,则会阻塞。
  3. 场景中两个事务都持有gap lock,然后又申请插入意向锁,此时都被阻塞,循环等待造成死锁。

锁兼容矩阵

锁兼容矩阵

死锁解决

方案如下几种选择:

  1. 不采用事务包装这部分逻辑,本文实际业务场景中可以不需要事务,所以直接取消事务包装即可,采用insert ON DUPLICATE KEY UPDATE的方式
  2. 调整事务隔离级别为read commit,RC级别不会产生gap lock
  3. 利用分布式锁
  4. sql改写
    select
    if [obj] not exist
    insert
    else
    select for update
    update

你可能感兴趣的:(select for update 并发insert死锁问题)