MySQL到底在RR层面解决幻读了吗?

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: hi,好久不见。最近工作上有一些变动,文章很少更新。不过平时还是有收集一些文章idea,后面有空会慢慢写~

Y说


hi,好久不见。

最近工作上有一些变动,文章很少更新。不过平时还是有收集一些文章idea,后面有空会慢慢写~

这篇文章是因为之前写了一篇关于InnoDB锁的文章,在个人网站上有读者留言问“间隙锁一定程度上解决了幻读问题,为什么不是完全解决了呢”,所以重新把MySQL中关于幻读的问题梳理了一遍。

文章写完后,发现自己也有新的收获,又了解了一些奇怪的知识点,哈哈。


隔离级别与幻读

数据库的事务有四种隔离级别,这四种隔离级别分别应用在不同的场景,隔离级别越低,并发性越高,但数据一致性就越差。四种隔离级别存在的问题如下表:

隔离级别 脏读 不可重复读 幻读
Read uncommitted
Read Committed
Repeatable Reads
Serializable

MySQL InnoDB默认的隔离级别是Repeatable Reads(RR),但它通过MVCC+间隙锁解决了绝大部分幻读(后面会解释为什么是绝大部分而不是全部)的问题。


什么是幻读

简单来说,一个事务同样的查询条件,两次查询到的数据行数不一样,它就产生了幻读。

举例来说,对于下面的数据:

id c d
5 5 5
10 10 10
15 15 15

事务A第一次查询sql:

SELECT * FROM demo WHERE id < 10;

这个时候只能查出 id = 5 的数据。

而这个时候如果事务B插入了一条id = 6的数据:

INSERT INTO demo VALUES (7, 7, 7);

然后A进行第二次查询,用同样的sql,如果查到了两条数据:id = 5 和 id = 7,那就是出现了幻读。对于事务A来说,就像幻觉一样,同样的查询条件,查出来的数据多了一行。

这个在业务场景中也很常见:先查询名为“编了个程”的公众号,发现不存在,于是我准备创建的时候,却发现已经被其它事务创建了,于是唯一索引提示我创建失败,改名称已存在。那我这个事务岂不是出现幻觉了?明明刚刚查过不存在的呀~


快照读和当前读

快照读

在MySQL中,查询语句分为两种,一种是简单的select操作,属于快照读,不加锁。它读的是记录的快照版本(这个版本跟MVCC有关,后面再细聊)。

select * from table where ?;

当前读

另一种是要加锁的特殊的读操作,它读的是记录的最新版本。

-- 共享读锁
select * from table where ? lock in share mode;
-- 共享写锁
select * from table where ? for update;
-- 增删改也属于当前读,因为要先看这条记录在不在
insert into table values (…);
update table set ? where ?;
delete from table where ?;


快照读下的幻读

InnoDB是通过MVCC来解决当前读下的幻读问题的。每个事务会有一个递增的事务ID,每行记录都有两个隐藏字段:创建版本和删除版本。在进行操作时,遵循以下规则:

INSERT

保存当前事务id作为行版本号

DELETE

保存当前的事务id到这行数据的“删除版本”。

UPDATE

插入一行新记录,保存当前事务id作为行版本号,同时保存当前事务id到原来的行的“删除版本”。

SELECT

  • 只读取版本小于等于当前事务id的行。这样可以保证事务读取都的行,要么之前就存在,要么是这个事务本身自己插入或者修改的。
  • 只读取“删除版本”为空或者小于等于当前事务id的行。这样可以确保事务读取到的行,在事务之前没有被删除。


当前读下的幻读

当前读下的幻读是通过间隙锁(gap_lock)来实现的。在事务A查询的时候,会锁住一个间隙,其它事务往这个间隙插入、删除等操作都是会被锁阻塞的。

关于间隙锁是如何工作的,可以参考我之前写的这篇文章:《InnoDB的行锁,原来为你做了这么多!》,简单来说,间隙锁和插入意向锁互斥,彻底解决了当前读下的幻读问题。


快照读没有完全解决幻读?

是的,MySQL没有完全解决快照读下的幻读问题。

可以做这个实验:

  1. 当前DB已有id 5, 10, 15三条数据。
  2. 事务A查询id < 10的数据,可以查出一行记录id = 5
  3. 事务B插入id = 6的数据
  4. 事务A再查询id < 10的数据,可以查出一行记录id = 5,查不出id = 6的数据(读场景,解决了幻读)
  5. 事务A可以更新/删除id = 6的数据,不能插入id = 6的数据(写场景,幻读不彻底)

这个很好理解,MySQL虽然通过MVCC的版本号来解决了读场景下的幻读,但对于上面第5步那种写场景的情况,其实是无能为力的,因为MVCC毕竟是无锁实现。

所以如果后续要对数据进行写操作,还是通过for update语句上锁比较稳妥,不然就可能会出现上面第5步那样的问题。

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
2天前
|
存储 监控 关系型数据库
Mysql内部在索引层面的优化
Mysql内部在索引层面的优化
|
10月前
|
存储 SQL 缓存
【MySQL】从执行流程以及日志层面介绍一条修改语句都做了什么
【MySQL】从执行流程以及日志层面介绍一条修改语句都做了什么
114 0
|
SQL 存储 网络协议
为什么MySQL默认的隔离级别是RR而大厂使用的是RC?
为什么MySQL默认的隔离级别是RR而大厂使用的是RC?
389 0
为什么MySQL默认的隔离级别是RR而大厂使用的是RC?
|
关系型数据库 MySQL 数据库
MySQL事务的隔离级别以及脏读、幻读和不可重复读
MySQL事务的隔离级别以及脏读、幻读和不可重复读
|
SQL 关系型数据库 MySQL
你知道MySQL是如何解决幻读的吗?
你知道MySQL是如何解决幻读的吗?
290 0
你知道MySQL是如何解决幻读的吗?
|
SQL Oracle 关系型数据库
Mysql模拟事物的隔离级别【脏读、不可重复读、幻读】
Mysql模拟事物的隔离级别【脏读、不可重复读、幻读】
153 0
Mysql模拟事物的隔离级别【脏读、不可重复读、幻读】
|
关系型数据库 MySQL 索引
MySQL中RR模式下死锁一例
MySQL中RR模式下死锁一例
168 0
MySQL中RR模式下死锁一例
|
关系型数据库 MySQL
MySQL:关于RR模式下insert..select sending data状态说明
MySQL:关于RR模式下insert..select sending data状态说明
110 0
|
SQL 关系型数据库 MySQL
【Mysql】数据库事务,脏读、幻读、不可重复读
【Mysql】数据库事务,脏读、幻读、不可重复读
【Mysql】数据库事务,脏读、幻读、不可重复读
|
SQL 关系型数据库 MySQL
【MySQL】可重复读隔离级别是否解决了幻读?
面试官:可重复读隔离级别是否解决了幻读了吗?不清楚是吧,没关系,那我们今天面试就先到这。
192 0
【MySQL】可重复读隔离级别是否解决了幻读?

推荐镜像

更多