delete,update,select for update如果没有命中到数据产生间隙锁

具体其他事务相关可以看数据库事务的隔离级别及常见事务异常 - 简书 (jianshu.com)

update,delete 或者是select for update 如果没有命中的到数据

如果没有命中到数据,而使用的条件又是带有索引的字段,会使用间隙锁+临键锁
并且极其容易产生死锁!
举个例子:
id name other_id node_id
55 啊 201 333
56 哈 210 333
57 哈 211 222
索引为联合 索引 other_id , node_id
如上述数据使用mysql默认可重复读隔离级别,使用如下语句
delete from table where other_id = 205 and node_id = 333
那么 现在获取排他锁,没有匹配到数据,并且使用索引字段作为条件,那么现在会利用这个索引的间隙锁锁住区间数据,并且产生了临键锁,遵循左开右闭并且联合索引锁住数据
这个时候会通过间隙锁+临建锁,虽然是联合索引,但是实际使用索引左值第一个other_id来检测数据锁的冲突。因为没有具体的数据可以排他,间隙锁大家都尝试去锁住一个区域,并不会互相排他等待,但是如果再insert这个间隙锁区域的数据时需要保证排他性。那么这个时候才会去互斥等待,如果前面有两个事务已经通过间隙锁尝试锁住这个区域了,两个事务会等待另一个间隙锁释放。则会产生死锁
上述delete语句会获取(201,210]的other_id的数据间隙锁,但是不会互斥,那么其他并发的事务如果同时获取这个区间的数据也会获取间隙锁,所以间隙锁是一个共享锁。待再insert table into (other_id, node_id) values(205,333) 会产生死锁。

具体例子如下

业务中我需要将原有的 other_id = 205 and node_id = 333 和 other_id = 206 and node_id = 666
删除,无论他有木有,再新建

  1. 事务(1)delete from table where other_id = 205 and node_id = 333
    sleep(8s)
    insert table into (other_id, node_id) values (205,333)
  2. 事务(2)delete from table where other_id = 207 and node_id = 666
    sleep(8s)
    insert table into (other_id, node_id) values (205,333)
    用两个接口测试,这个连个事务会死锁。(sleep是拉长事务时间,提高锁竞争概率,所以说有可能产生死锁的代码也是并发足够/事务时间长度两个因素暴露出来)
    那么如果使用 other_id = 300 则会获取 (211, 无穷大 所有大于211的间隙锁,更容易产生死锁。
解决方案,如果不是update,并且强制需要原有数据的状态判断的依赖,完全可以使用select(不要使用for update)获取共享锁,后续delete,update使用id,如果没有数据也不会去继续操作数据库了,这样就不会出现间隙锁啦!当然,如果没有这个数据你还用一个没有的id去delete,还是会以这个id生成间隙锁,但是没有代码会这样写吧 - -!,如果select 通过共享锁事务拿到数据,就算其他事务也同时进行了删除,那么这个时候delete语句就会有互斥性。并且不会产生间隙锁

select * from table where other_id = 205 and node_id = 333
如果不为null 再用 id delete
再 insert table into (other_id, node_id) values (205,333)
select 不会产生间隙锁

总结

一切排他锁 例如 update, delete 尽量使用 byId一定不会是间隙锁。如果是一定需要竞争查询到库里的数据,并依赖库里的数据状态值,需要用到select for update 尽量保证库里的数据存在,后面再继续操作,如果不存在,会产生间隙锁,如果继续操作极有可能死锁

如上操作如果你的where条件没有索引,那就是缩表了。。。百分百死锁,哈哈哈

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

推荐阅读更多精彩内容