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

问题分析

首先幻读是什么?

根据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中的锁进行介绍。

最后

大家有什么想法,可以一起讨论!

参考链接:

https://dev.mysql.com/doc/refman/8.0/en/innodb-next-key-locking.html

https://www.cnblogs.com/f-ck-need-u/archive/2018/05/08/9010872.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,978评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,954评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,623评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,324评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,390评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,741评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,892评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,655评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,104评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,569评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,254评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,834评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,725评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,950评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,260评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,446评论 2 348