先说结论,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. Shell系列(21)- 字符截取命令printf

    作用 printf是标准格式输出命令,控制输出格式,不会自动加入换行符.awk会用到该条命令 命令 printf '输出类型输出格式' 输出内容 #''双引号不能少,输出类型和输出格式之间没有空格 输 ...

  2. 接口Mock测试

    什么是Mock测试? Mock 测试就是在测试过程中,对于某些不容易构造(如 HttpServletRequest 必须在Servlet 容器中才能构造出来)或者不容易获取的比较复杂的对象(如 JDB ...

  3. python学习笔记(八)-模块

    大型python程序以模块和包的形式组织.python标准库中包含大量的模块.一个python文件就是一个模块.1.标准模块 python自带的,不需要你安装的2.第三方模块 需要安装,别人提供的. ...

  4. Spotlight监控工具的使用

    Spotlight下载地址:http://spotlight-on-unix.software.informer.com/download/#downloading Spotlight是Quest公司 ...

  5. 大前端快闪二:react开发模式 一键启动多个服务

    最近全权负责了一个前后端分离的web项目,前端使用create-react-app, 后端使用golang做的api服务. npx create-react-app my-app cd my-app ...

  6. VUE -input输入框字母转大写

    示例: 输入自动转--->大写 <input type="text" placeholder="请输入证件号码" maxlength="1 ...

  7. B站1024程序员节部分答案

    1.页面的背后是什么? 直接撸页面源码就行啦 2.真正的秘密只有特殊的设备才能看到 修改UA为页面上提示的"bilibili Security Browser" 3.密码是啥? 弱 ...

  8. 打开属性页,分别在Debug和Release下将其配置类型改为:静态库(.lib);

    右键工程->属性 配置类型里面的下拉菜单选择静态库

  9. 数据应用的变与不变,ShardingSphere 正在影响未来数字体验的建设理念

    近年来关于底层数据库的开源产品越来越多,它们也受到了许多资本的青睐. 伴随着移动互联网催生的数字化场景爆发,云计算.大数据等技术逐渐有了更加广阔的应用场景.在云计算和大数据经过十年的追赶式发展后,不只 ...

  10. Kotlin/Native KMM项目架构

    一.什么是KMM? Kotlin Multiplatform Mobile ( KMM ) 是一个 SDK,旨在简化跨平台移动应用程序的创建.在 KMM 的帮助下,您可以在 iOS 和 Android ...