mysql怎么避免幻读 mysql是如何解决幻读

Mysql:RR隔离级别下的幻读众所周知,Mysql在InnoDB下有四种隔离级别:
未提交读(Read Uncommitted)
提交后读(Read Committed)
可重复读(Repeatable Read)
串行化(Serializable)
其中可重复读(RR)可以避免脏读( a事务读到b事务回滚前的数据)以及可不重复读( a事务在b事务修改提交的前后,两次分别读到的数据不一致) 。但是对于幻读(a事务在b事务insert提交前后,两次分别读到的数据不一致),却存在争议 。
下面我们来做一个试验:
对于下面这张简单的数据表
idnum
111
222
333
我们开启a、b两个事务
a事务b事务
beginbegin
select * from tb----
----insert into tb (id,num)values(4,44)
----commit
select * from tb----
commit
试验结果:a事务的两次select查询到的结果相同,在后一次查询中没有返回新插入id=4的那条记录 。
据此,很多人判断说RR隔离级别下“不存在”幻读 。
但果真如此吗?----
出现上面的试验结果,是因为在RR隔离级别事务下,Mysql会对前一次select的结果快照 。所以第二次select其实是快照读(这也正是RR隔离级别下能够避免不可重复读的策略) 。
如果我们把试验条件稍作修改,同样开启a、b两个事务:
a事务b事务
beginbegin
select * from tb----
----insert into tb (id,num)values(5,55)
【mysql怎么避免幻读 mysql是如何解决幻读】 ----commit
update tb set num=num 1----#此处a事务做一次修改操作
select * from tb----
commit
试验结果:在a事务的第二次select中出现了b事务新插入的id=5的记录 。
由于做了update操作,之前的快照失效了,所以说RR隔离级别下的快照策略并没能真正避免幻读 。
ps. 假如给第二次的select查询上锁(无论是共享锁还是排它锁),也会得到同样的结果,都会令快照失效 。
关于MySQL的幻读问题,看这一篇就够了什么是幻读mysql怎么避免幻读?
幻读指mysql怎么避免幻读的是一个事务在前后两次查询同一个范围mysql怎么避免幻读的时候,后一次查询看到了前一次查询没有看到的行 。
首先快照读是不存在幻读的,只有当前读(实时读)才存在幻读的问题 。
幻读有什么问题?
select ...for update语句就是将相应的数据行锁住 , 但是如果存在幻读,就把for update的语义破坏了 。
如何解决幻读?
产生幻读的原因是,行锁只能锁住行,但是新插入记录这个动作,要更新的是记录之间的“间隙” 。因此,为了解决幻读问题 , InnoDB只好引入新的锁,也就是间隙锁(Gap Lock) 。间隙锁和行锁合称 next-key lock,每个next-key lock是前开后闭区间。
总结
mysql可重复读的幻读解决方案 首先需要明确的就是“幻读”概念mysql怎么避免幻读: 隔离级别是可重复读mysql怎么避免幻读,在一个事务中前后两次查询mysql怎么避免幻读,查到了其他事务insert进来的数据 。
强调的是读取到了其他事务插入进来的数据 。
下面来论证一下可重复读下幻读的解决方案
先明确一下mysql怎么避免幻读,for update语法就是当前读,也就是查询当前已经提交的数据,并且是带悲观锁的 。没有for update就是快照读,也就是根据readView读取的undolog中的数据 。
如果按照以上猜想 , 那么整个执行结果就违背了 可重复读 的隔离级别了 。
那么mysql怎么避免幻读我们再假设select * from TABLE where d = 5 for update;这条语句锁定的是所有被扫描到的数据 。
这是因为T2阶段的update会被阻塞?。?毕竟所有被扫描到的记录都被锁定了 。
按照上述推理过程,很显然,即使锁定所有扫描到的数据行 , 也依然存在幻读的情况 。违背了 可重复读 的隔离级别 。
针对这个情况,我们要解决幻读的问题 , 那么就要求针对所有被扫描的记录行以及还不存在的d=5的记录行都给锁住 。
至此,当前查询结果完全满足 可重复读 的隔离级别 。
通过以上推论 , 我们可以总结一下,在可重复读的隔离级别下,解决幻读除了需要锁定所有扫描到的记录行外,还需要锁定行之间的间隙 , 也就是通过间隙锁来解决幻读的问题 。
mysql 解决可提交读、可重复读、幻读这张图本人觉得总结得挺好mysql怎么避免幻读的,在一般的互联网项目中,基本上用的都是Innodb引擎,一般只涉及到的都是行级锁,但是如果sql语句中不带索引进行操作,可能会导致锁表,这是不推荐的,性能非常低,可能会导致全表扫描等,行锁的具体实现算法有以下几种mysql特有的锁:
Record Lock(记录锁):单个行记录的锁,一般是唯一索引或者主键上的加锁
Gap Lock(间隙锁):锁定一个区间 , 但是不包括自身,开区间的锁 , RR级别才会有间隙锁,间隙锁的唯一目的是防止区间数据的插入,所以间隙锁与间隙锁之间是不会相互阻塞的
Next-key Lock(临键锁):与间隙锁的区别是包括自身,是左开右闭区间,RR级别才会有
加锁规则里面,包含了两个“原则”、两个“优化”和一个“bug” 。
原则 1:加锁的基本单位是 next-key lock,希望你还记得,next-key lock 是前开后闭区间 。
原则 2:查找过程中访问到的对象才会加锁 。
优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁 。
优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁 。
一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止 。
举例来说明上述的原则:
建表
插入数据:
INSERT INTOt ( id ,c ,d ) VALUES (0, 0, 0);
INSERT INTOt ( id ,c ,d ) VALUES (5, 5, 10);
INSERT INTOt ( id ,c ,d ) VALUES (10, 10, 10);
INSERT INTOt ( id ,c ,d ) VALUES (15, 15, 15);
INSERT INTOt ( id ,c ,d ) VALUES (20, 20, 20);
INSERT INTOt ( id ,c ,d ) VALUES (25, 25, 25);
例子1:锁表
因为d字段上没有建索引,所以涉及该字段的查询加锁会锁住整个表
因为d字段上面没有建立索引 , 所以事务1执行后会导致整个表被锁 , 后面所有的操作都会在等待整个表锁被释放
例子2:主键/唯一索引 记录锁
id字段为主键,而且事务1查询命中了唯一的记录 , 默认是加Next-key Lock,区间是(0,5],但是根据优化1,唯一索引/主键上的等值查询 , 会退化为行锁,所以只会锁5这个记录 。
例子3:主键/唯一索引上的间隙锁
由于表 t 中没有 id=7 的记录,所以用mysql怎么避免幻读我们上面提到的加锁规则判断一下的话:根据原则 1,加锁单位是 next-key lock , 事务1加锁范围就是 (5,10];同时根据优化 2,这是一个等值查询 (id=7),而 id=10 不满足查询条件,next-key lock 退化成间隙锁,因此最终加锁的范围是 (5,10),所以事务2会阻塞,事务3执行成功 。
例子4:普通索引上的间隙锁
c字段是普通索引,事务1执行时默认是对区间(0,5]加间隙锁 , 根据优化2,非唯一索引/主键会继续向右遍历,找到10 , 所以最终的加锁为(0,5]的Next-Key锁 (5,10)的间隙锁,所以事务2阻塞,事务3成功 。
例子5:间隙锁与行锁
事务1默认的Next-Key锁区间是(0,5],根据优化2会向右遍历,找到不满足查询条件的10,退化成间隙锁,所以事务1的锁是(0,5]的Next-Key锁 (5,10)的间隙锁,这两个锁与行锁是冲突的,而事务2申请的Next-Key锁是和事务1一样,但是c=5的行锁与事务1冲突,所以产生了阻塞,如果改为update t set d=1000 where c=6;因为此时产生的间隙锁为(5,10),而间隙锁与间隙锁是不冲突的,不会产生阻塞
例子6:lock in share mode锁覆盖索引
事务1存在覆盖索引的情况,不会去回表,lock in share mode这种情况下只会锁c字段索引,而事务2是对主键加行锁,所以两者不存在冲突 。
例子7:主键/唯一索引上的范围查询
开始执行的时候,要找到第一个 id=10 的行 , 因此本该是 Next-Key Lock(5,10],根据优化 1,主键 id 上的等值条件,退化成行锁,只加了 id=10 这一行的行锁 。范围查找就往后继续找 , 找到 id=15 这一行停下来,因此需要加 Next-Key Lock(10,15],所以事务3是冲突的 。
例子8:普通索引上的范围查询
开始执行时,找到第一个满足条件的行10,加锁Next-Key Lock(5,10] , 因为不是唯一索引,所以不会退化,继续向后面找,找到15这一行停下来 , 因此需要加 Next-Key Lock(10,15],因为是范围查询,所以锁不会退化 。
快照读: 通过MVCC实现,该技术不仅可以保证innodb的可重复读,而且可以防止幻读,但是他读取的数据虽然是一致的,但是数据是历史数据 。
简单的select操作(不包括 select … lock in share mode, select … for update)
当前读: 要做到保证数据是一致的,同时读取的数据是最新的数据,innodb提供了next-key lock,即gap锁与行锁结合来实现 。
select … lock in share mode
select … for update
insert
update
delete
自己理解:
简单的select是快照读,快照读实现可提交读,可重复读和幻读是通过MVCC ReadView实现的,而当前读实现这几种是通过锁来实现的,为了说明具体原理,下面介绍下MVCC和ReadView概念,所以简单的select是通过乐观锁实现的 , 当前读是通过悲观锁实现的 。
参考文章:
关于mysql怎么避免幻读和mysql是如何解决幻读的介绍到此就结束了 , 不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站 。

    推荐阅读