什么是幻读?
幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行。
首先快照读是不存在幻读的,只有当前读(实时读)才存在幻读的问题。
幻读有什么问题?
select ...for update语句就是将相应的数据行锁住,但是如果存在幻读,就把for update的语义破坏了。
如何解决幻读?
产生幻读的原因是,行锁只能锁住行,但是新插入记录这个动作,要更新的是记录之间的“间隙”。因此,为了解决幻读问题,InnoDB只好引入新的锁,也就是间隙锁(Gap Lock)。间隙锁和行锁合称 next-key lock , 每个next-key lock是前开后闭区间 。
总结
接上篇 事务隔离级别和幻读 ,留了个坑,没想到竟然过了10天,时间不注意真的过的好快。顺便提下,图片链接是属于网站的,开发自己的图床迫在眉睫,万一哪天迁移就要做很多额外工作,一些概念或者思路用图片表达更直观清楚。
回到正题,之前提到一般情况下MySQL的InnoDB引擎在可重复读的情况下是没法保证不出现幻读的,但实际情况是MySQL可以通过加锁来防止幻读的出现,这种锁定通过Next-key机制来实现,是属于记录锁和间隙锁(Gap锁)的结合。
引申,行级别锁的三种算法:
举个存在唯一索引和辅助索引的例子做说明:
执行 select * from test where b = 3 for update
存在两个索引,分别加锁,唯一主键列a加record lock , 辅助索引列b加next-key lock (1,3) 以及给下一个值的区间(3,6)加gap锁;
因此在另一个事务里执行以下语句都会阻塞,具体分析:
第一个阻塞因为加了唯一索引的record lock a = 5;
第二个主键插入4,符合条件,但是根据辅助索引b 的范围, b = 2 在(1,3)中,同样阻塞;
第三个a =6 不在主键a锁定范围,b = 5 也不在辅助索引b 的范围(1,3)中,但在另一个gap锁范围(3,6)中,因此也阻塞;
这种锁定情形下,可以执行的包括类似语句:
insert的特殊情况
对于insert 会检查下一条记录是否被锁定,如上述例子有 select * from test where b = 3 for update 插入 insert into test select 2,2 会检测到b = 3 已经被锁定,而 insert into test select 2,0 可以执行;
[1]:《MySQL技术内幕:InnoDB存储引擎》-第六章:锁
幻读的定义是指,一个事务开启后,执行前后两次查询,两次查询中出现了新的数据,幻读仅针对数据的新增。
比如: 表t中,id为主键,目前有数据1,5,10,20四条。
开始一个事务A,前后两次执行 select * from t where id <10 for update
开启一个事务B,在事务A第二次执行查询前,执行insert into t values( 2,...)并提交事务(请暂时忽略这里能否成功执行!)。
此时在RC、RR隔离级别下都会导致事务A第二次查询能够查询到 事务B新增的数据 id = 2。
RC级别下能够看到不同结果就不做解释了。
对于RR隔离级别下,有了MVCC版本控制为什么还能读取到不同的结果呢?
这里要归功于 "for update"。
"for update" 会将快照读变为当前读,在当前读场景中,会自动读取最新的数据,而非快照数据。
分析一下,锁与当前读关系。了解什么情况下会加锁。了解 意向锁、共享锁、排它锁区别及各自在什么情况下使用。
行锁的概念都清楚,这里就不做补充了。
间隙锁实际上是指一个区间。
我们都知道,InnoDB 在RR事务隔离级别下解决幻读问题就是通过Next Key Lock (间隙锁+行锁)来实现的。而且,很多地方也有提到,如果对于读一致性要求不高的场景可以考虑使用RC隔离级别,允许幻读的发生。
还是上边说的那个实例,略微改动:
比如: 表t中,id为主键,目前有数据1,5,10,20四条。
开始一个事务A,前后三次分别执行
开启一个事务B,在事务A执行update前,执行insert into t values( 2,...)并提交事务。
此时我们知道,事务A中第二次查询能够查到 事务B新增的数据,也就是产生了幻读。那么,按照SQL执行的顺序来说,事务B
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)