微信搜索“coder-home”或扫一扫下面的二维码,关注公众号,第一时间了解更多干货分享,还有各类视频教程资源。扫描它,带走我


文章目录

  • 背景
  • 疑问点
  • 分析
    • 什么是快照读
      • 开启事务命令的区别
    • 什么当前读
    • 什么是幻读
  • MySQL到底有没有解决幻读
    • 在只有快照读前提下,不会发生幻读
    • 当前读的时候,可能会发生幻读
    • 当前读中发生的幻读是如何解决的
  • 总结

背景

我最近一直被一个问题困扰着:MySQL在其默认的可重复读(Read-Committed)RR隔离级别下,到底有没有解决幻读的问题?

看了网上很多的贴子,感觉每一个帖子都有一个观点,越看越感觉混乱。有的说是在RR级别下,MySQL的幻读不会发生;有的说RR级别下,MySQL中幻读会发生。各执一词,并且每一个观点都不能给出一些有说服性的例子。所以,打算自己去分析一下到底是否解决了幻读的问题。

疑问点

我的疑惑只要有以下几点:

  1. 什么是快照读?
  2. 什么是当前读?
  3. 什么是幻读?
  4. 如果说MySQL解决了幻读,那为什么在当前读下面还会发生幻读的问题?
  5. 幻读的解决是不是需要在SQL中去使用某种方式才可以“启用”避免幻功能,会不会是默认不启动的?

分析

什么是快照读

说到快照读,就得先说一下快照,而说到快照,就得说说MVCC。

快照是属于MVCC中的一个概念。在RR级别下,MySQL通过MVCC的技术会给每一个事务在启动的时候,创建一个一致性的快照视图,这个快照中的所有数据内容就是这个事务在启动时刻的生成的,后续数据库中的数据再怎么变化,这个快照的数据内容都不受它们的影响。而这个快照一直会伴随着整个事务的生命周期。在这个事务运行过程中的,所有的普通查询都会从这个快照中去获取数据,事务中的这些普通的查询就属于快照读。

举例说明一下什么是快照读,下面事务中的几个查询语句都是属于快照读。

start transaction with consistent snapshot; -- begin/start transaction命令也可以
do something...
select * from t; -- 快照读
do something...
select * from t where id = 1; -- 快照读
do something...
select * from t where name = 'zhangsan'; -- 快照读
do something...
commit;

开启事务命令的区别

说明:上面的开启事务的方式,没有使用begin或start transaction命令,而是使用了start transaction with consistent snapshot命令启动的事务。其实,这两个命名都是可以开启事务的。但是它们之间有一点点区别。

start transaction with consistent snapshot命令会在事务开启之后马上就创建MVCC一致性视图。而使用begin或start transaction命令启动的事务,会在开启事务后,第一次操作InnoDB表的SQL语句后,才会创建MVCC一致性视图,这里的操作InnoDB的SQL语句可以是insert、update、delete、select中的任何一个,但是要求是操作的InnoDB存储引擎的表,不能是其他引擎的表。

通过下面的两个实验截图来说明这两个命令的区别。

begin开启事务如下:

上面的第6步中,你可能会感觉到很奇怪:为什么第6步可以看到右侧事务新增加的数据行呢?在RR级别下,左侧的事务启动后,在事务运行期间和结束后,应该看不到右侧事务新增加的行才对?这不就是发生不可重复读的问题了吗?这和我们平时所说的RR级别下,MySQL是支持可重复的结论相违背呀?

其实不违背我们平时说的RR下面MySQL支持可重复的结论。之所以出现上面的这个情况的根本原因是MVCC一致性视图在一个事务当中,创建的时间点是什么时候?是事务开启之后就创建了?还是在事务执行的过程中在某一个动作之后才创建。如果你再上面实验的步骤3之后,不直接去执行步骤4,而是在左侧事务中执行一个和步骤3一样的查询动作,我们暂时称为步骤3.1,如果你再左侧的事务中,3.1步骤,那么久不会发生上面左侧事务读取到右侧事务中插入数据的现象了。因为这个3.1的动作就会触发创建一致性视图的动作,而此时左侧事务创建的MVCC视图中的数据就不会包含右侧事务步骤4插入的数据行。

我们分析一下上面的这个过程:

  • 步骤6是发生在右侧事务开启并结束之后,是步骤1执行完成后,我们就没有在左侧事务中执行任何操作,而是直接在右侧事务中一口气的把右侧的事务执行结束。
  • 此时数据库的表已经有(5,55)这一行数据了。
  • 而在步骤6中,此时是左侧事务在执行完begin命令之后,第一次操作innodb类型的表的SQL语句。此时执行SQL语句的时候才去创建的MVCC一致性视图。
  • 在创建一致性视图的时候,是会获取数据库中最新的已经提交的数据作为MVCC视图中的数据。所以在步骤6这个SQL语句中,会查询出来右侧事务中已经提交的(5,55)这一行数据。

start transaction with consistent snapshot开启的事务如下:

什么当前读

在一个事务执行的过程中,如果我们这个时候使用了DML语句,也就是我们平时所说的insert、update、delete语句,此时DML会执行当前读,它们会在操作数据库内容之前,去读取数据库中当前时间点以及提交的最新的数据,基于最新的数据的基础上,再去做这个DML语句自己的SQL逻辑。此时的这个读取数据库中最新已提交的数据的这个动作,就是当前读。

我们拿一个事务当中的update语句来说,在修改数据的时候,需要先读到数据,才能基于读到的数据上再去做修改。而这个读取数据的时候,需要基于数据库中最新的已经提交的数据来做,如果此时仍然按照一致性快照读,那么会读取到当前事务开启的时候所能读取到的数据版本,而这个数据版本有可能已经不是最新的了,其他事务可能已经在这个数据版本的基础上进行的修改,如果不去数据库中读取其他事务更改后的数据,那么此时就会覆盖掉其他事务的修改操作。而这是数据库中锁不允许的。所以,在修改的时候,要执行当前读,然后再修改。

在一个事务当中,使用DML语句操作数据库就是属于当前读的范畴。例如使用如下的SQL语句就是属于当前读。

begin;
do something...
insert into t values(11,'zhangsan'); -- 会发生当前读
do something...
update t set name = 'zhang' where id = 11; -- 会发生当前读
do something...
delete from t where id = 11; -- 会发生当前读
do something...
commit;

除了我们平时使用的DML之外,如下两个SQL语句也属于当前读的范畴:

begin;
do something...
select * from t where id = 1 lock in share mode; -- 会发生当前读
do something...
select * from t where id = 2 for update; -- 会发生当前读
do something...
commit;

什么是幻读

幻读是基于插入的操作而言的。更新、删除操作不属于幻读的范畴,属于不可重复读的范畴。

当前事务在运行的过程中,一开始的时候没有读取到其他事务插入的行,但是后来读取到了其他事务插入数据,这才是幻读。读取到其他事务更新、删除的操作内容,不是幻读,而是不可重复读。

MySQL到底有没有解决幻读

我所的困惑点只要有以下几个:

  • MySQL在RR隔离级别下,幻读到底会不会发生?
  • 如果会发生,那么MySQL能避免这种情况的发生吗?
  • 如果可以避免,那它是使用什么方式来避免的呢?

带着以上这3个问题,我们来逐步做实验来验证一下。下面所有的实验都是在MySQL5.7版本的RR事务隔离级别下进行的。

在只有快照读前提下,不会发生幻读

也就是说,如果在事务执行过程中,全部都是使用的一致性快照读,他们读取的数据都是从快照视图中读取的数据,此时的数据就是在事务开始的时候创建好的,在事务执行的过程中,任何时候只要是从快照中去读取,那么数据永远都是一样的,不会发生变化。所以说,在快照读的情况下,不会发生幻读,如下所示:

注意:在一个事务当中,如果有一次或多次发生了当前读,就有可能会发生幻读。例如先开始的时候是执行的快照读,后来执行了一次因为更新语句而发生的当前读,然后再次执行快照读,就有可能发生幻读。

这里的有可能是有这几个前提:

  1. 一个事务中,快照读和当前读混合使用。
  2. 在执行当前读之前,另外一个事务插入了新的数据。
  3. 在当前这个事务中,执行当前读的时候,查询的范围结果中包含了另外一个事务的插入数据。
  4. 再次执行快照读,幻读就会发生。
begin;
select * from t; -- 快照读
-- 在此时间点,当前事务下面的update语句还没有执行的时候,如果有另外一个事务,向t表中插入的一条数据,然后当前事务再去执行更新全表数据的语句,更新完成后,再次执行快照对,就有可能会发生幻读。
update t set a = a + 1; -- 更新语句,会先执行当前读再去更改数据。
select * from t; -- 此时的快照读有可能发生幻读。
commit;

当前读的时候,可能会发生幻读

  1. 开启左侧事务A
  2. 开启右侧事务B
  3. 是左侧事务A中查询表t
  4. 在右侧事务B中查询表t
  5. 在右侧事务B中,向表t插入一行数据(5,55)
  6. 在右侧事务B中,查询表t,可以查询到自己刚插入的新数据。
  7. 在左侧事务A中,查询表t,看不到事务B中刚插入的数据。
  8. 提交右侧的事务B
  9. 在右侧事务B提交后的窗口中,再次查询表t,可以看到刚插入的数据行。
  10. 在左侧事务A中,查询表t,仍然查询不到右侧事务B所插入并提交的数据。
  11. 在左侧的事务A中,更新表中所有的数据,不使用任何条件。此时发现输入的影响结果的行数不是前面事务A所能查询到的4行数据,而是输出影响了5行数据,多了一行。此时在更新的时候,使用了当前度的功能,把刚才右侧事务B中插入并提交的一行数据(5,55)也给查询出来并其修改掉了。所以在提示结果中,显示影响行数为5行。此时已经发生了幻读,事务A读取到了事务B的插入的数据。
  12. 在右侧事务B结束的窗口中,继续查询表t,发现可以看到5行数据,但是数据内容不是左侧事务A修改的结果,此时正常,因为左侧的事务A还没有提交。
  13. 在左侧事务A中,再次查询表t的数据,发生此时的结果为5行数据。包含了右侧事务B插入且提交的数据行(5,55),并且它被上面的update语句给修改为了(5,5500)。此时发生了幻读。

实验截图如下:

注意:把上面截图中的第11步更新全表数据的操作,换成如下SQL语句,也会发生幻读。上面的试验操作是把右侧事务新插入的行通过update语句给修改了,下面的两个SQL分别是在左侧事务中尝试再次插入同一行数据,和删除右侧事务新插入的数据。

/*
在左侧事务中,再次尝试插入右侧事务已经插入且提交的数据行的试试,会提示主键冲突的错误,但是查询整个表的数据又发现没有这样的主键值的行,但是就是插入不成功。这也是幻读的一种体现。
*/
insert into t values(5,55);/*
在左侧事务中,尝试删除右侧事务中插入且提交的数据,此时发现影响行数为1行,我们预期的应该是影响行数为0行,因为我们前面查询的时候,并没有id=5的这样的行存在,但是删除的时候却提示删除成功了。并且提交左侧事务后,在右侧已经结束的事务窗口中再次查询表t,发现之前可以看到的新插入的数据,确实不存在了,被左侧的事务给删除掉了。
*/
delete from t where id = 5;

当前读中发生的幻读是如何解决的

针对前面我们在当前读中锁发生的幻读的现象,MySQL在RR下面,到底能否解决这样的幻读问题呢?答案是肯定的,是通过间隙锁来实现的。

原理就是在我的事务将要操作的表上,除了增加行锁之外,增加间隙锁,让其在这些间隙中,不能插入数据,然后再我的事务后面即便是我执行了当前读也不会发生幻读的现象了。

实验如下:

注意:上面的第5步中是给表增加了表级别的S锁。这里的加锁方式取决于我们的SQL语句是什么样子的,如果是for update语句,那就是增加X锁,如果是lock in share mode就是S锁,这是指锁的类型。那么锁的粒度或者说是范围是什么样子的呢?

至于锁的粒度范围也是取决于我们的SQL语句和我们的表结构设计的。

  • 如果SQL语句中的where条件,使用到了索引,那么就是增加行锁和间隙锁或临键锁。
  • 如果SQL语句中的where条件,没有使用到了索引,使根据一个普通的列筛选条件,那么将会降级为表锁。把整个表都给锁上了。

这里比较复杂,有各种加锁的方式和规则。后续单独分享一下这里的加锁的逻辑。

总结

所以,到目前为止,我们的疑惑目前应该依据清楚了。

  • MySQL在RR隔离级别下,幻读到底会不会发生?会的。
  • 如果会发生,那么MySQL能避免这种情况的发生吗?能。
  • 如果可以避免,那它是使用什么方式来避免的呢?显示的使用间隙锁。

MySQL在RR隔离级别下,是有可能会发生幻读的。这里的有可能是有以下前提条件的。

  1. 在一个事务当中,快照读和当前读都使用到了。
  2. 与此同时,其他事务刚好有插入数据的操作发生且提交了。
  3. 当前读在当前事务中发生的时间点是在其他事务插入数据且提交之后。
  4. 然后在当前事务中再次执行快照读或当前读的时候,如果查询条件搜索的范围刚好可以包含其他事务插入的数据,则会发生幻读。

严格意义上,MySQL是在一定程度上修复了幻读。为了修复幻读的问题,它提供了间隙锁。在事务中,操作数据之前,我们给我们要操作的数据范围增加上了正确的间隙锁就可以避免幻读的发生。

MySQL提供了修复幻读机制:间隙锁。但是需要业务自己去加锁,如果不加锁,只是简单的SELECT查询,是无法限制并行事务的插入数据的。

而这个加锁的功能,不是MySQL自动就给增加上的,需要结合我们自己的实际业务场景,有选择性的去“开启”这个功能,当我们去开启了这个加锁的功能后,从而可以避免幻读的发生。只是锁数据的粒度、范围是MySQL自己控制的,它会根据我们的表结构、主键、唯一索引、普通索引、普通数据列、SQL语句的where条件等关系,来自动决定锁数据的粒度是使用行锁、还是间隙锁、还是临键锁、还是表锁。

开启这个功能的方式就是在我们的事务中,当前读之前,先去获取锁,然后再去做DML的当前读。

例如下面两个语句,第一个是不能避免大于10的id行在其他事务中别插入的动作,而第二个SQL是可以避免其他事务在执行完该语句后再次向表t中插入id大于10的记录。其实这就是启用的间隙锁的功能,避免了后续出现幻读的可能性。

select * from t where id > 10;
select * from t where id > 10 for update;

微信搜索“coder-home”或扫一扫下面的二维码,关注公众号,第一时间了解更多干货分享,还有各类视频教程资源。扫描它,带走我


MySQL在RR级别下到底有没有修复幻读相关推荐

  1. 【十四】MySQL Innodb RR隔离级别下到底是不是解决了幻读

    我之前一直质疑网传mysql innodb的RR隔离级别下,next-key lock解决了幻读这种说法的准确性. 这次理顺了. 例子准备: 场景一.select * from user order ...

  2. mysql rr 更新失败_RR 级别下 update 操作的是快照读还是当前读?

    我们知道在 RR 级别下,重复的 select 操作,读取的值都会是一致的.即便在两次 select 操作的中间,有一个事务 B 修改了值,但是在事务 A 中 select 读取的值还是一致的. 那么 ...

  3. Mysql RR级别下如何解决幻读

    快照读 在RR级别下,Mysql是根据MVCC来解决快照读时发生的幻读现象,简单来说就是利用了版本链以及Read View来实现的,RR下只有事务刚一开始时才会产生Read View,后续都会使用这个 ...

  4. RR级别下的GAP锁范围

    故不尽知用兵之害者,则不能尽知用兵之利也. ​ 对于索引,给人的第一印象可能是查询性能的提高,再者是更新或插入之后的sort/merge开销以及页分裂问题(聚簇索引也是索引). 还有一些同学说索引对于 ...

  5. MySQL 到底是怎么解决幻读的?

    作者:LastSun https://www.cnblogs.com/wdy1184/p/10655180.html 一.什么是幻读 在一次事务里面,多次查询之后,结果集的个数不一致的情况叫做幻读.而 ...

  6. MySQL到底是如何解决幻读问题

    要知道什么是幻读,首先要知道以下四点: 一.幻读定义 幻读是指在同一个事务中,存在前后两次查询同一个范围的数据,但是第二次查询却看到了第一次查询没看到的行,一般情况下特指事务执行中新增的其他行. 二. ...

  7. Spring事务配置的五种方式和spring里面事务的传播属性和事务隔离级别、不可重复读与幻读的区别

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. spring事务配置的五种方式 前段时间对Spring的事务配置做了比较深入的研究,在此之间对Spr ...

  8. 面试 | 你说你熟悉MySql,那你就来谈谈InnoDB如何解决幻读的?

    这是小小本周的第一篇. 今天干了啥 今天可是周日,一个休息日,对于休息日来说,小小本身也是比较忙碌的,忙碌的小小,耗费的很多的时间,终于倒腾完成了GitChat,一篇GitChat 将会于近日出炉.完 ...

  9. MySQL理论:脏读、不可重复读、幻读

    文章目录 1. 脏读(dirty read) 脏读是指事务读取到其他事务未提交的数据 2. 不可重复读(non-repeatable read) 不可重复读是指在同一次事务中前后查询不一致的问题 3. ...

最新文章

  1. 30分钟 Keras 创建一个图像分类器
  2. ubuntu KDE桌面
  3. 学习jquery选项卡插件
  4. 基于 MySQL + Tablestore 分层存储架构的大规模订单系统实践-架构篇
  5. 区块链溯源系统架构---区块链工作笔记002
  6. 使 32 位程序使用大于 2GB 的内存
  7. 概述Swing窗体的种类
  8. 2021-09-02语义分割 实例分割 全景分割
  9. nowcoder猜想c语言筛子,剑指 Offer 50 道经典算法题视频讲解
  10. 免费的Web压力测试工具
  11. 选择排序(直接排序)
  12. 2018南邮全国计算机大赛,我院承办2018全国大学生物联网设计竞赛(TI杯)南京邮电大学选拔赛...
  13. Photoshop CS5无法卸载或卸载不干净怎么办?
  14. html document怎么转换成word,如何将HTML document文件类型转换成word document?
  15. ASP.NET Core MVC 之局部视图(Partial Views)
  16. firefox正在安装组件,以便播放此页面上的音频或视频
  17. PowerDesigner 模型生成转化为sql脚本
  18. 命运交响曲计算机弹奏,贝多芬命运交响曲弹奏方法和介绍-雅马哈电子琴排行榜...
  19. access2007 mysql_access2007使用方法,access2007使用教程
  20. 托勒密定理 圆的内接四边形

热门文章

  1. 数据库分库分表,分片配置轻松入门!
  2. 浙大版《C语言程序设计(第3版)》题目集总表
  3. JAVA基础知识练习(减肥计划、逢七过、不死神兔、百钱百鸡、数组元素求和、数组内容相同、查找、反转、评委打分)
  4. mysql vchar 磁盘碎片_mysql TEXT与BLOB 碎片整理
  5. 扇贝python编程课_【扇贝编程python安卓手机下载】扇贝编程app v1.1.47 破解版-趣致软件园...
  6. WiFi-ESP8266入门http(3-1)网页认证上网-post请求(原教程)
  7. DSNet: A Flexible Detect-to-Summarize Network for Video Summarizationa论文笔记
  8. 五月总结 时光待我不薄
  9. 最新人工智能GPT-4免费简单使用教程
  10. 【Educoder作业】绘制炸弹轨迹 I——绘制一个坐标点