之前介绍过,sql标准中,rr级别并不能解决幻读问题,那么mysql是如何在rr级别解决幻读问题的?
锁协议
在非序列化隔离级别下,普通读数据是快照读,写数据则要加X锁,并且遵循两段式锁协议。也就是申请的锁会一直占用,直到事务提交。
幻读问题
幻读的定义有两点需要注意:
1.幻读只有使用锁定读才可能出现,因为普通读使用的是非锁定读,会通过MVCC机制获取到一致性视图,是看不到新插入的行的;
2.只有读到新增的数据才叫幻读;
幻读的影响:
1.如果只有行锁的话,语义不正确,没有锁住新更新的行;
2.一致性,主库和从库的状态可能不一致;
间隙锁
上面的X锁其实是行锁,即对某一行数据加锁;这个可以防止脏写的发生,即锁住已经存在的行,但是却无法阻止新插入的行。间隙锁是专门用来锁住某一个间隙的,是开区间;
next-key lock
是间隙锁+行锁的组合,间隙锁是行锁key之前的那个间隙。比如行锁要锁住id=5的行,前一个值是id=1的行,那么该行锁对应的间隙就是(1,5),next-key lock就是(1,5]。
在rr级别下,默认的加锁类型就是next-key lock。这样可以防止新插入的数据导致的幻读问题。因为加的是next-key lock,所以锁定的范围变大了,相应得,并发性能也会降低,这需要权衡。
在其他级别下加的是行锁。比如我们在rc级别下,就可以用锁定读复现幻读问题:
事务一:
先执行一条锁定读,因为是在rc级别,只给已经存在的id=1这一行加了行X锁;
事务二:
也插入了一条gender=1的数据,因为是新插入的,所以不受事务一加的行锁阻塞;
事务一:
紧接着,事务一再执行一条锁定读,可以看到,读到了新插入的数据,发生了幻读;
所以,仅仅在rr级别下才会加next-key lock,在rc级别仍然是行锁,是会有幻读问题的;
next-key lock加锁规则
这个比较复杂,需要结合具体的场景分析了:
访问到的对象加next-key lock,直到不满足条件的第一个值,这个值的锁退化为间隙锁;如果是索引上的等值查询,退化为行锁;