具体其他事务相关可以看数据库事务的隔离级别及常见事务异常 - 简书 (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)delete from table where other_id = 205 and node_id = 333
sleep(8s)
insert table into (other_id, node_id) values (205,333) - 事务(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 不会产生间隙锁