问题分析

首先幻读是什么?

根据MySQL文档上面的定义

The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a SELECT is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row.

幻读指的是在一个事务内,同一SELECT语句在不同时间执行,得到不同的结果集时,就会发生所谓的幻读问题。

可以看看下面的例子:

这是网上找的一张图(事务的务字写错了,不过不影响我们理解)

假设这个例子中的MySQL的隔离级别是提交读,也就是一个事务内可以读到其他事务提交后的结果。

那么事务1第一次查询dept表中所有部门时,结果是没有"研发部",但是由于隔离级别是提交读,在事务2插入“研发部”这一行数据后,并且提交后,事务1是可以读取到的,所以第二次查询时,结果集中会有“研发部”。这就是幻读。

SELECT语句分类

首先我们的SELECT查询分为快照读和实时读,快照读通过MVCC(并发多版本控制)来解决幻读问题,实时读通过行锁来解决幻读问题。

快照读

1.1 快照读是什么?

因为MySQL默认的隔离级别是可重复读,这种隔离级别下,我们普通的SELECT语句都是快照读,也就是在一个事务内,多次执行SELECT语句,查询到的数据都是事务开始时那个状态的数据(这样就不会受其他事务修改数据的影响),这样就解决了幻读的问题。

1.2 那么innodb是怎么解决快照读的幻读问题的?

快照读就是每一行数据中额外保存两个隐藏的列,插入这个数据行时的版本号,删除这个数据行时的版本号(可能为空),滚动指针(指向undo log中用于事务回滚的日志记录)。

事务在对数据修改后,进行保存时,如果数据行的当前版本号与事务开始取得数据的版本号一致就保存成功,否则保存失败。

当我们不显式使用BEGIN来开启事务时,我们执行的每一条语句就是一个事务,每次开始事务时,会对系统版本号+1作为当前事务的ID。

1.2.1插入操作

插入一行数据时,将事务的ID作为数据行的创建版本号。

1.2.2删除操作

执行删除操作时,会将原数据行的删除版本号设置为当前事务的ID,然后根据原数据行生成一条INSERT语句,写入undo log,用于事务执行失败时回滚。delete操作实际上不会直接删除,而是将delete对象打上delete flag,标记为删除,最终的删除操作是purge线程完成的。但是会将数据行的删除版本号设置为当前的事务的ID,这样后面的事务B即便查到这行数据由于事务B的ID>删除版本号,也会忽略这条数据。

1.2.3更新操作

更新时可以简单的认为是先将旧数据删除,然后插入一条新数据。

所以执行更新操作时,其实是会将原数据行的删除版本号设置为当前事务的ID,生成一条INSERT语句,写入undo log,用于事务执行失败时回滚。插入一条新的数据,将事务的ID作为数据行的的创建版本号。

1.2.4查询操作

数据行要被查询出来必须满足两个条件,

  • 数据行删除版本号为空或者>当前事务版本号的数据(否则数据已经被标记删除了)

  • 创建版本号<=当前事务版本号的数据(否则数据是后面的事务创建出来的)

简单来说,就是查询时,

  • 如果该行数据没有被加行锁中的X锁(也就是没有其他事务对这行数据进行修改),那么直接读取数据(前提是数据的版本号<=当前事务版本号的数据,不然不会放到查询结果集里面)。
  • 该行数据被加了行锁X锁(也就是现在有其他事务对这行数据进行修改),那么读数据的事务不会进行等待,而是回去undo log端里面读之前版本的数据(这里存储的数据本身是用于回滚的),在可重复读的隔离级别下,从undo log中读取的数据总是事务开始时的快照数据(也就是版本号小于当前事务ID的数据),在提交读的隔离级别下,从undo log中读取的总是最新的快照数据。

1.3 补充资料:undo log段是什么?

undo_log是一种逻辑日志,是旧数据的备份。有两个作用,用于事务回滚和为MVCC提供老版本的数据。

可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录。

1.3.1.用于事务回滚

当事务执行失败,回退时,会读取这行数据的滚动指针(指向undo log中用于事务回滚的日志记录),就可以在undo log中找到相应的逻辑记录,读取到相应的回滚语句,执行进行回滚。

1.3.2.为MVCC提供老版本的数据

当读取的某一行被其他事务锁定时(也就是有其他事务正在改这行数据),它可以从undo log中分析出该行记录以前的数据是什么,从而提供该行版本信息,让用户进行快照读。在可重复读的隔离级别下,从undo log中读取的数据总是事务开始时的快照数据(也就是版本号小于当前事务ID的数据),在提交读的隔离级别下,从undo log中读取的总是最新的快照数据(也就是比正在修改这行数据的事务ID修改前的数据。)。

实时读

2.1实时读是什么?

如果说快照读总是读取事务开始时那个状态的数据,实时读就是查询时总是执行这个查询时数据库中的数据。

一般使用以下这两种查询语句进行查询时就是实时读。

SELECT *** FOR UPDATE 在查询时会先申请X锁SELECT *** IN SHARE MODE 在查询时会先申请S锁

首先看一个实时读产生幻读的案例:





这是《MySQL技术内幕++InnoDB存储引擎++第2版》里面的一张图,就是先将隔离级别设置为提交读,这样第一次执行 SELECT...FOR UPDATE查询出来的数据是a:4,事务B插入了一条新的数据,再次执行 SELECT...FOR UPDATE语句时,查询出来就是a:4,a:5两条数据,这就是幻读的问题。

2.1那么innodb是怎么解决实时读的幻读问题的?

如果我们不在一开始将将隔离级别设置为提交读,其实是不会产生幻读问题的,因为MySQL的默认隔离级别是可重复读,在这种情况下,我们执行第一次 SELECT...FOR UPDATE查询语句是,其实是会先申请行锁,因为一开始数据库就只有a:4一行数据,那么加锁区间其实是

(负无穷,4](4,正无穷)

我们查询条件是a>2,上面两个加锁区间都会可能有数据满足条件,所以会申请行锁中的next-key lock,是会对上面这两个区间都加锁,这样其他事务不能往这两个区间插入数据,事务B会执行插入时会一直等待获取锁,直到事务A提交,释放行锁,事务B才有可能申请到锁,然后进行插入。这样就解决了幻读问题。

如果大家对行锁了解得比较少,下一期会对innodb中的锁进行介绍。

最后

大家有什么想法,可以一起讨论!本文已收录到1.1K Star数开源学习指南——《大厂面试指北》,如果想要了解更多大厂面试相关的内容,了解更多可以看

http://notfound9.github.io/interviewGuide/#/docs/BATInterview

img

【大厂面试03期】MySQL是怎么解决幻读问题的?的更多相关文章

  1. MySQL 是如何解决幻读的

    MySQL 是如何解决幻读的 一.什么是幻读 在一次事务里面,多次查询之后,结果集的个数不一致的情况叫做幻读. 而多出来或者少的哪一行被叫做 幻行 二.为什么要解决幻读 在高并发数据库系统中,需要保证 ...

  2. 何为幻读?MySQL又是如何解决幻读的?

    一.什么是幻读 在一次事务里面,多次查询之后,查询的结果集的个数不一致的情况叫做幻读.而多出来或者少的哪一行被叫做 幻行 二.为什么要解决幻读 在高并发数据库系统中,需要保证事务与事务之间的隔离性,还 ...

  3. MySQL是怎么解决幻读问题的?

    前言 我们知道MySQL在可重复读隔离级别下别的事物提交的内容,是看不到的.而可提交隔离级别下是可以看到别的事务提交的.而如果我们的业务场景是在事物内同样的两个查询我们需要看到的数据都是一致的,不能被 ...

  4. MySQL到底能否解决幻读问题

    先说结论,MySQL 存储引擎 InnoDB 在可重复读(RR)隔离级别下是解决了幻读问题的. 方法:是通过next-key lock在当前读事务开启时,1.给涉及到的行加写锁(行锁)防止写操作:2. ...

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

    ; 原理:将历史数据存一份快照,所以其他事务增加与删除数据,对于当前事务来说是不可见的. 2. next-key 锁 (当前读) next-key 锁包含两部分: 记录锁(行锁) 间隙锁 记录锁是加在 ...

  6. 【大厂面试02期】Redis过期key是怎么样清理的?

    PS:本文已收录到1.1K Star数开源学习指南--<大厂面试指北>,如果想要了解更多大厂面试相关的内容,了解更多可以看 http://notfound9.github.io/inter ...

  7. 【Java面试】这应该是面试官最想听到的回答,Mysql如何解决幻读问题?

    "Mysql如何解决幻读问题" 一个工作了4年小伙伴,去一个美团面试,遇到了这样一个问题. 大家好,我是Mic,一个工作了14年的Java程序员 关于这个问题,面试官想考察什么?我 ...

  8. 面试官:MySQL的可重复读级别能解决幻读问题吗?

    引言 之前在深入了解数据库理论的时候,了解到事务的不同隔离级别可能存在的问题.为了更好的理解所以在MySQL数据库中测试复现这些问题.关于脏读和不可重复读在相应的隔离级别下都很容易的复现了. 但是对于 ...

  9. 【面试普通人VS高手系列】innoDB如何解决幻读

    前天有个去快手面试的小伙伴私信我,他遇到了这样一个问题: "InnoDB如何解决幻读"? 这个问题确实不是很好回答,在实际应用中,很多同学几乎都不关注数据库的事务隔离性. 所有问题 ...

随机推荐

  1. EEGLAB-批量处理.dat数据及保存脑电地形图

    步骤 1.先在图形界面操作一遍准备做的操作. 2.在命令行窗口输入 EEG.history 获取刚刚操作都用到哪些语句. 3.稍加修改即可以写一个批量化函数来读取生成数据. 4.在 EEGLAB\ee ...

  2. Spark Streaming 基本操作

    Spark Streaming 基本操作 ​ 一.案例引入        3.1 StreamingContext        3.2 数据源        3.3 服务的启动与停止二.Transf ...

  3. Hive环境搭建和SparkSql整合

    一.搭建准备环境 在搭建Hive和SparkSql进行整合之前,首先需要搭建完成HDFS和Spark相关环境 这里使用Hive和Spark进行整合的目的主要是: 1.使用Hive对SparkSql中产 ...

  4. Fragment 嵌套Fragment注意事项

    最近项目新功能需要在垂直方方向可以循环滚动,并且水平方向也可以水平循环滚动,并且可以定位到指定item上.很自然的想到了ViewPager和 VerticalViewPager来解决项目需求,UI的大 ...

  5. flex和flex:1的含义

    一.flex详解 flex可以参考阮一峰老师的flex布局教程,很详细看完啥都懂了 链接:http://www.ruanyifeng.com/blog/2015/07/flex-grammar.htm ...

  6. 适配器模式C++实现

    目录 类适配器 对象适配器 类适配器 #include <iostream> using namespace std; // Target class Target { public: v ...

  7. Linux系统rabbitmq安装

    rabbitmq消息队列(Message Queue)是一种应用间的通信方式,消息发送后可以立即返回,由消息系统来确保消息的可靠传递.消息发布者只管把消息发布到 MQ 中而不用管谁来取,消息使用者只管 ...

  8. HDU6035 Colorful Tree

    题目链接:https://vjudge.net/problem/HDU-6035 题目大意: 多样例输入. 对于每一个样例,给出 n \((2 \le n \le 200000)\) 个结点的一棵树, ...

  9. Istio 将被捐赠给开源基金会 | 云原生生态周报 Vol. 47

    作者 | 陈俊.徐迪.陈有坤.李鹏.敖小剑 业界要闻 1.Google Cloud CEO 表示将把 Istio 项目捐赠给基金会 Istio 项目找到了理想的发展方向: 捐赠给开源基金会. 2.Ko ...

  10. 好用的python性能测试神器–Locust

    原文链接:https://mp.weixin.qq.com/s/9PxSPuHmucSLi_welq6uNQ 现在性能测试工具太多,根据业务不同使用,比如说我们熟悉的loadrunner.jmeter ...