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
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。