先说结论,MySQL 存储引擎 InnoDB 在可重复读(RR)隔离级别下是解决了幻读问题的。

方法:是通过next-key lock在当前读事务开启时,1.给涉及到的行加写锁(行锁)防止写操作;2.给涉及到的行两端加间隙锁(Gap Lock)防止新增行写入;从而解决了幻读问题。

下面,让我带大家从原理出发,一起来搞懂MySQL并发问题 -- “幻读”。如果有好的看法,咱们评论见吧。

目录

什么是幻读

一、幻读定义

二、幻读示例

三、幻读出现的场景

四、解决幻读问题的必要性

如何解决幻读

一、原理解读

二、next-key lock

一张照片的故事


什么是幻读

要知道什么是幻读,首先要知道以下四点:

一、幻读定义

幻读是指在同一个事务中,存在前后两次查询同一个范围的数据,但是第二次查询却看到了第一次查询没看到的行,一般情况下特指事务执行中新增的其他行。

二、幻读示例

测试表数据:

mysql> select * from LOL;
+----+--------------+--------------+-------+
| id | hero_title | hero_name | price |
+----+--------------+--------------+-------+
| 1 | 刀锋之影 | 泰隆 | 6300 |
| 2 | 迅捷斥候 | 提莫 | 6300 |
| 3 | 光辉女郎 | 拉克丝 | 1350 |
| 4 | 发条魔灵 | 奥莉安娜 | 6300 |
| 5 | 至高之拳 | 李青 | 6300 |
| 6 | 无极剑圣 | 易 | 450 |
| 7 | 疾风剑豪 | 亚索 | 6300 |
+----+--------------+--------------+-------+
7 rows in set (0.00 sec)

下面是一个出现幻读情况示例,我们一起来看一下;

时刻T Session A Session B Session C
T1

begin;

-- Query1

select * from LOL where price=450 for update;

Result:(6,'无极剑圣',450)

   
T2   update LOL set price=450 where hero_title = '疾风剑豪'  
T3

-- Query2

select * from LOL where price=450 for update;

Result:(6,'无极剑圣',450),(7,'疾风剑豪',450)

   
T4     insert into LOL values(10,'雪人骑士','努努','450')
T5

-- Query3

select * from LOL where price=450 for update;

Result:(6,'无极剑圣',450),(7,'疾风剑豪',450),(10,'雪人骑士',450)

   
T6 commit;    

可以看到,session A 里执行了三次查询,分别是 Q1、Q2 和 Q3。它们的 SQL 语句相同,都是 select * from LOL where price=450 for update。这个语句的意思你应该很清楚了,查所有 price=450 的行,而且使用的是当前读,并且加上写锁。现在,我们来看一下这三条 SQL 语句,分别会返回什么结果。

  1. Q1 只返回 "无极剑圣" 这一行;
  2. 在 T2 时刻,session B 把 "疾风剑豪" 这一行的 price 值改成了 450,因此 T3 时刻 Q2 查出来的是 "无极剑圣""疾风剑豪" 这两行;
  3. 在 T4 时刻,session C 又插入一行 (10,'雪人骑士','努努','450'),因此 T5 时刻 Q3 查出来 price = 450 的是"无极剑圣" 、"疾风剑豪" 和 "雪人骑士" 这三行。

其中,Q3 读到 (10,'雪人骑士',450) 这一行的现象,被称为“幻读”。也就是说,幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行。

三、幻读出现的场景

  1. 幻读出现在可重复读(RR)隔离级别下,普通的SELECT查询就是快照读,是不会看到别的事务插入的数据的。因此,幻读在“当前读”下才会出现。(当前读会生成行锁,但行锁只能锁定存在的行,针对新插入的操作没有限定)
  2. 上面 session B 的修改结果,被 session A 之后的 select 语句用“当前读”看到,不能称为幻读。幻读仅专指“新插入的行”。

因为这三个查询都是加了 for update,都是当前读。而当前读的规则,就是要能读到所有已经提交的记录的最新值。并且,session B 和 sessionC 的两条语句,执行后就会提交,所以 Q2 和 Q3 就是应该看到这两个事务的操作效果,而且也看到了,这跟事务的可见性规则并不矛盾。

四、解决幻读问题的必要性

在高并发数据库系统中,需要保证事务与事务之间的隔离性,还有事务本身的一致性。

如何解决幻读

如果你看到了这篇文章,那么我会默认你了解了脏读 、不可重复读与可重复读。如果还不清楚可以先参阅《上个厕所的功夫,搞懂MySQL事务隔离级别》

场景如上,场景隔离级别为RR,当前读。

一、原理解读

那么幻读能仅通过行锁解决么?答案是否定的,如上面示例,首先说明一下,select xx for update(当前读)是将所有条件涉及到的(符合where条件)行加上行锁。但是,就算我在select xx for update 事务开启时将所有的行都加上行锁。那么也锁不住Session C新增的行,因为在我给数据加锁的时刻,压根就还没有新增的那行,自然也不会给新增行加上锁。

所以要解决幻读,就必须得解决新增行的问题。

现在你应该明白了,产生幻读的原因是:行锁只能锁住行,但是新插入记录这个动作,要更新的是记录之间的“间隙”。因此,为了解决幻读问题,InnoDB 只好引入新的锁,也就是间隙锁 (Gap Lock)。顾名思义,间隙锁,锁的就是两个值之间的空隙。比如文章开头的表 LOL,初始化插入了 7 个记录,这就产生了 8 个间隙。

二、next-key lock

这样,当你执行 select * from LOL where hero_title = '疾风剑豪' for update 的时候,就不止是给数据库中已有的 7 个记录加上了行锁,还同时加了 8 个间隙锁。这样就确保了无法再插入新的记录,也就是Session C在T4新增(10,'雪人骑士','努努','450') 行时,由于ID大于7,被间隙锁(7,+∞)锁住。

在一行行扫描的过程中,不仅将给行加上了行锁,还给行两边的空隙,也加上了间隙锁。MySQL将行锁 + 间隙锁组合统称为 next-key lock,通过 next-key lock 解决了幻读问题。

注意

next-key lock的确是解决了幻读问题,但是next-key lock在并发情况下也经常会造成死锁。死锁检测和处理也会花费时间,一定程度上影响到并发量。

参考资料

《高性能MySQL》

《丁奇MySQL实战45讲》

一张照片的故事

或许京剧自己都没想到
清末的洋人,民国的战火都没能毁了它
最后居然是衰落在中国人自己的手里

MySQL到底能否解决幻读问题的更多相关文章

  1. MySQL 是如何解决幻读的

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

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

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

  3. 【大厂面试03期】MySQL是怎么解决幻读问题的?

    问题分析 首先幻读是什么? 根据MySQL文档上面的定义 The so-called phantom problem occurs within a transaction when the same ...

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

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

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

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

  6. MySQL到底有没有解决幻读问题?这篇文章彻底给你解答

    MySQL InnoDB引擎在Repeatable Read(可重复读)隔离级别下,到底有没有解决幻读的问题? 网上众说纷纭,有的说解决了,有的说没解决,甚至有些大v的意见都无法达成统一. 今天就深入 ...

  7. MySQL的可重复读级别能解决幻读吗

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

  8. MYSQL如何解决幻读

    第一部分 首先要了解下mysql数据库的事务特征之一隔离级别: READ UNCOMMITTED(未提交读): 在READUNCOMMITTED级别,事务中的修改,即使没有提交,对其他事务也都是可见的 ...

  9. MySQL锁(三)行锁:幻读是什么?如何解决幻读?

    概述 前面两篇文章介绍了MySQL的全局锁和表级锁,今天就介绍一下MySQL的行锁. MySQL的行锁是各个引擎内部实现的,不是所有的引擎支持行锁,例如MyISAM就不支持行锁. 不支持行锁就意味着在 ...

随机推荐

  1. linux环境下,使用Navicat连接mysql时,提示本地IP无法连接虚拟环境下的mysql解决方案

    在Linux环境下,使用Navicat连接mysql时,提示本地IP无法连接虚拟环境下的mysql,提示如下: 而导致连接错误的原因是MYSQL没有开启远程登录权限. 解决方案: 在mysql命令中执 ...

  2. [转载]让你玩转代码的编辑神器phpstorm功能详解

    转载网址:http://wwwquan.com/show-66-121-1.html phpstorm包含了webstorm的全部功能,更能够支持php代码.PhpStorm是一个轻量级且便捷的PHP ...

  3. Apache ShardingSphere 邀您相约 Open Source Day

    Apache ShardingSphere 很荣幸接受 AnitaB.org 的邀请参加 OpenSourceDay Summer 2021 活动. Open Source Day (OSD) 是一个 ...

  4. js Promise用法

    Promise 英文意思是 承诺的意思,是对将来的事情做了承诺, Promise 有三种状态, Pending 进行中或者等待中 Fulfilled 已成功 Rejected 已失败 Promise ...

  5. 洛谷3195 [HNOI2008]玩具装箱TOY(斜率优化+dp)

    qwq斜率优化好题 第一步还是考虑最朴素的\(dp\) \[dp=dp[j]+(i-j-1+sum[i]-sum[j])^2 \] 设\(f[i]=sum[i]+i\) 那么考虑将上述柿子变成$$dp ...

  6. VMware中Linux虚拟机与Windows主机共享文件夹

    VMware下Linux虚拟机与Windows主机共享文件夹 1. 安装vm-tool 2. 开启共享文件夹 虚拟机->设置->选项->共享文件夹"右边选择"总是 ...

  7. Java(24)常用API三

    作者:季沐测试笔记 原文地址:https://www.cnblogs.com/testero/p/15228417.html 博客主页:https://www.cnblogs.com/testero ...

  8. c语言中for循环 和嵌套for循环

    for循环:for( ; ; )里面是bai3个语句,两个分号.第一个语句是开始前执行,第二个语句是判断真假,如果真,就执行后面(大括号内)的代码.第三个语句是每次执行完毕后执行的东西,通常第三个语句 ...

  9. 初学python-day11 函数3

    函数 1. global关键字 修改全局变量,声明函数内外使用同一个变量 示例: 1 name = 'xiaoming' 2 3 def test(): 4 global name 5 name = ...

  10. Coursera Deep Learning笔记 改善深层神经网络:超参数调试 Batch归一化 Softmax

    摘抄:https://xienaoban.github.io/posts/2106.html 1. 调试(Tuning) 超参数 取值 #学习速率:\(\alpha\) Momentum:\(\bet ...