MySQL脏读,幻读和不可重复读


Posted in MySQL onMay 11, 2022

MySQL 中事务的隔离

在 MySQL 中事务的隔离级别有以下 4 种:

  • 读未提交(READ UNCOMMITTED)
  • 读已提交(READ COMMITTED)
  • 可重复读(REPEATABLE READ)
  • 序列化(SERIALIZABLE)

MySQL 默认的事务隔离级别是可重复读(REPEATABLE READ),这 4 种隔离级别的说明如下。

1.READ UNCOMMITTED

读未提交,也叫未提交读,该隔离级别的事务可以看到其他事务中未提交的数据。该隔离级别因为可以读取到其他事务中未提交的数据,而未提交的数据可能会发生回滚,因此我们把该级别读取到的数据称之为脏数据,把这个问题称之为脏读。

2.READ COMMITTED

读已提交,也叫提交读,该隔离级别的事务能读取到已经提交事务的数据,因此它不会有脏读问题。但由于在事务的执行中可以读取到其他事务提交的结果,所以在不同时间的相同 SQL 查询中,可能会得到不同的结果,这种现象叫做不可重复读。

3.REPEATABLE READ

可重复读,是 MySQL 的默认事务隔离级别,它能确保同一事务多次查询的结果一致。但也会有新的问题,比如此级别的事务正在执行时,另一个事务成功的插入了某条数据,但因为它每次查询的结果都是一样的,所以会导致查询不到这条数据,自己重复插入时又失败(因为唯一约束的原因)。明明在事务中查询不到这条信息,但自己就是插入不进去,这就叫幻读 (Phantom Read)。

4.SERIALIZABLE

序列化,事务最高隔离级别,它会强制事务排序,使之不会发生冲突,从而解决了脏读、不可重复读和幻读问题,但因为执行效率低,所以真正使用的场景并不多。​

简单总结一下,MySQL 的 4 种事务隔离级别对应脏读、不可重复读和幻读的关系如下:

事务隔离级别 脏读 不可重复读 幻读
读未提交(READ UNCOMMITTED)
读已提交(READ COMMITTED) ×
可重复读(REPEATABLE READ) × ×
串行化(SERIALIZABLE) × × ×

只看以上概念会比较抽象,接下来,咱们一步步通过执行的结果来理解这几种隔离级别的区别。

前置知识

1.事务相关的常用命令

# 查看 MySQL 版本
select version();
# 开启事务
start transaction;
# 提交事务
commit;
# 回滚事务
rollback;

2.MySQL 8 之前查询事务的隔离级别

查看全局 MySQL 事务隔离级别和当前会话的事务隔离级别的 SQL 如下:

select @@global.tx_isolation,@@tx_isolation;

以上 SQL 执行结果如下图所示: 

MySQL脏读,幻读和不可重复读

3.MySQL 8 之后查询事务的隔离级别

select @@global.transaction_isolation,@@transaction_isolation;

4.查看连接的客户端详情

每个 MySQL 命令行窗口就是一个 MySQL 客户端,每个客户端都可以单独设置(不同的)事务隔离级别,这也是演示 MySQL 并发事务的基础。以下是查询客户端连接的 SQL 命令:

show processlist;

以上 SQL 执行结果如下: 

MySQL脏读,幻读和不可重复读

5.查询连接客户端的数量

可以使用以下 SQL 命令,查询连当前接 MySQL 服务器的客户端数量:

show status like 'Threads%';

以上 SQL 执行结果如下: 

MySQL脏读,幻读和不可重复读

6.设置客户端的事务隔离级别

通过以下 SQL 可以设置当前客户端的事务隔离级别:

set session transaction isolation level 事务隔离级别;

事务隔离级别的值有 4 个:READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE。

7.新建数据库和测试数据

创建测试数据库和表信息,执行 SQL 如下:

-- 创建数据库
drop database if exists testdb;
create database testdb;
use testdb;
-- 创建表
create table userinfo(
  id int primary key auto_increment,
  name varchar(250) not null,
  balance decimal(10,2) not null default 0
);
-- 插入测试数据
insert into userinfo(id,name,balance) values(1,'Java',100),(2,'MySQL',200);

创建的表结构和数据如下: 

MySQL脏读,幻读和不可重复读

8.名称约定

接下来会使用两个窗口(两个客户端)来演示事务不同隔离级别中脏读、不可重复读和幻读的问题。其中左边的黑底绿字的客户端下文将使用“窗口 1”来指代,而右边的蓝底白字的客户端下文将用“窗口 2”来指代,

如下图所示: 

MySQL脏读,幻读和不可重复读

脏读

一个事务读到另外一个事务还没有提交的数据,称之为脏读。 脏读演示的执行流程如下:

执行步骤 客户端1(窗口1) 客户端2(窗口2) 说明
第 1 步   set session transaction isolation level read uncommitted;start transaction;select * from userinfo; 设置事务隔离级别为读未提交;开启事务;查询用户列表,其中 Java 用户的余额为 100 元。
第 2 步 start transaction;update userinfo set balance=balance+50 where name='Java';   开启事务;给 Java 用户的账户加 50 元;
第 3 步   select * from userinfo; 查询用户列表,其中 Java 用户的余额变成了 150 元。

1.脏读演示步骤1

设置窗口 2 的事务隔离级别为读未提交,设置命令如下:

set session transaction isolation level read uncommitted;

PS:事务隔离级别读未提交存在脏读的问题。

然后使用命令来检查当前连接窗口的事务隔离界别,如下图所示: 

MySQL脏读,幻读和不可重复读

 开启事务并查询用户列表信息,如下图所示: 

MySQL脏读,幻读和不可重复读

2.脏读演示步骤2

在窗口 1 中开启一个事务,并给 Java 账户加 50 元,但不提交事务,执行的 SQL 如下: 

MySQL脏读,幻读和不可重复读

3.脏读演示步骤3

在窗口 2 中再次查询用户列表,执行结果如下: 

MySQL脏读,幻读和不可重复读

 从上述结果可以看出,在窗口 2 中读取到了窗口 1 中事务未提交的数据,这就是脏读。

4.不可重复读

不可重复读是指一个事务先后执行同一条 SQL,但两次读取到的数据不同,就是不可重复读。

不可重复读演示的执行流程如下:

执行步骤 客户端1(窗口1) 客户端2(窗口2) 说明
第 1 步   set session transaction isolation level read committed;start transaction;select * from userinfo; 设置事务隔离级别为读已提交;开启事务;查询用户列表,其中 Java 用户的余额是 100 元。
第 2 步 start transaction;update userinfo set balance=balance+20 where name='Java';commit;   开启事务;给 Java 用户的余额加 20 元;提交事务。
第 3 步   select * from userinfo; 查询用户列表,其中 Java 用户的余额变成了 120 元。

窗口 2 同一个事务中的两次查询,得到了不同的结果这就是不可重复读,具体执行步骤如下。

5.不可重复读演示步骤1

设置窗口 2 的事务隔离级别为读已提交,设置命令如下:

set session transaction isolation level read committed;

PS:读已提交可以解决脏读的问题,但存在不可重复读的问题。

使用命令来检查当前连接窗口的事务隔离界别,如下图所示: 

MySQL脏读,幻读和不可重复读

在窗口 2 中开启事务,并查询用户表,执行结果如下: 

MySQL脏读,幻读和不可重复读

 此时查询的列表中,Java 用户的余额为 100 元。

6.不可重复读演示步骤2

在窗口 1 中开启事务,并给 Java 用户添加 20 元,但不提交事务,再观察窗口 2 中有没有脏读的问题,

具体执行结果如下图所示: 

MySQL脏读,幻读和不可重复读

 从上述结果可以看出,当把窗口的事务隔离级别设置为读已提交,已经不存在脏读问题了。 接下来在窗口 1 中提交事务,执行结果如下图所示: 

MySQL脏读,幻读和不可重复读

7.不可重复读演示步骤3

切换到窗口 2 中再次查询用户列表,执行结果如下: 

MySQL脏读,幻读和不可重复读

 从上述结果可以看出,此时 Java 用户的余额已经变成 120 元了。在同一个事务中,先后查询的两次结果不一致就是不可重复读。

8.不可重复读和脏读的区别

脏读可以读到其他事务中未提交的数据,而不可重复读是读取到了其他事务已经提交的数据,但前后两次读取的结果不同。

幻读

幻读名如其文,它就像发生了某种幻觉一样,在一个事务中明明没有查到主键为 X 的数据,但主键为 X 的数据就是插入不进去,就像某种幻觉一样。

幻读演示的执行流程如下:

执行步骤 客户端1(窗口1) 客户端2(窗口2) 说明
第 1 步   set session transaction isolation level repeatable read;start transaction;select * from userinfo where id=3; 设置事务隔离级别为可重复读;开启事务;查询用户编号为 3 的数据,查询结果为空。
第 2 步 start transaction;insert into userinfo(id,name,balance) values(3,'Spring',100);commit;   开启事务;添加用户,用户编号为 3;提交事务。
第 3 步   insert into userinfo(id,name,balance) values(3,'Spring',100); 窗口 2 添加用户编号为 3 的数据,执行失败。
第 4 步   select * from userinfo where id=3; 查询用户编号为 3 的数据,查询结果为空。

具体执行结果如下步骤所示!

1.幻读演示步骤1

设置窗口 2 为可重复读,可重复有幻读的问题,查询编号为 3 的用户,具体执行 SQL 如下:

set session transaction isolation level repeatable read;
start transaction;
select * from userinfo where id=3;

以上 SQL 执行结果如下图所示: 

MySQL脏读,幻读和不可重复读

 从上述结果可以看出,查询的结果中 id=3 的数据为空。

2.幻读演示步骤2

开启窗口 1 的事务,插入用户编号为 3 的数据,然后成功提交事务,执行 SQL 如下:

start transaction;
insert into userinfo(id,name,balance) values(3,'Spring',100);
commit;

以上 SQL 执行结果如下图所示: 

MySQL脏读,幻读和不可重复读

3.幻读演示步骤3

在窗口 2 中插入用户编号为 3 的数据,执行 SQL 如下:

insert into userinfo(id,name,balance) values(3,'Spring',100);

以上 SQL 执行结果如下图所示: 

MySQL脏读,幻读和不可重复读

添加用户数据失败,提示表中已经存在了编号为 3 的数据,且此字段为主键,不能添加多个。

4.幻读演示步骤4

在窗口 2 中,重新执行查询:

select * from userinfo where id=3;

以上 SQL 执行结果如下图所示: 

MySQL脏读,幻读和不可重复读

/ 在此事务中查询明明没有编号为 3 的用户,但插入的时候却却提示已经存在了,这就是幻读。

5.不可重复读和幻读的区别

二者描述的则重点不同,不可重复读描述的侧重点是修改操作,而幻读描述的侧重点是添加和删除操作。

总结

本文演示了 MySQL 的 4 种事务隔离级别:读未提交(有脏读问题)、读已提交(有不可重复读的问题)、可重复读(有幻读的问题)和序列化,其中可重复读是 MySQL 默认的事务隔离级别。脏读是读到了其他事务未提交的数据,而不可重复读是读到了其他事务已经提交的数据,但前后查询的结果不同,而幻读则是明明查询不到,但就是插入不了。

到此这篇关于一文搞懂MySQL脏读,幻读和不可重复读的文章就介绍到这了!


Tags in this post...

MySQL 相关文章推荐
MySQL复制问题的三个参数分析
Apr 07 MySQL
解决MySQL存储时间出现不一致的问题
Apr 28 MySQL
.Net Core导入千万级数据至Mysql的步骤
May 24 MySQL
详解MySQL中的主键与事务
May 27 MySQL
SQL注入的实现以及防范示例详解
Jun 02 MySQL
MySQL 使用索引扫描进行排序
Jun 20 MySQL
mysql timestamp比较查询遇到的坑及解决
Nov 27 MySQL
一文弄懂MySQL索引创建原则
Feb 28 MySQL
MySQL分区路径子分区再分区
Apr 13 MySQL
SQL语句多表联合查询的方法示例
Apr 18 MySQL
分享很少见很有用的SQL功能CORRESPONDING
Aug 05 MySQL
MySQL count(*)统计总数问题汇总
Sep 23 MySQL
MySql数据库 查询时间序列间隔
May 11 #MySQL
Mysql中常用的join连接方式
May 11 #MySQL
MySQL的prepare使用以及遇到的bug
May 11 #MySQL
MySQL批量更新不同表中的数据
May 11 #MySQL
mysql查找连续出现n次以上的数字
May 11 #MySQL
mysql如何查询连续记录
May 11 #MySQL
mysql 体系结构和存储引擎介绍
You might like
PHP数学运算与数据处理实例分析
2016/04/01 PHP
centos+php+coreseek+sphinx+mysql之一coreseek安装篇
2016/10/25 PHP
微信 开发生成带参数的二维码的实例
2016/11/23 PHP
javascript编程起步(第六课)
2007/01/10 Javascript
alixixi runcode.asp的代码不错的应用
2007/08/08 Javascript
mvvm双向绑定机制的原理和实现代码(推荐)
2016/06/07 Javascript
JavaScript实现无刷新上传预览图片功能
2017/08/02 Javascript
elementui的默认样式修改方法
2018/02/23 Javascript
JavaScript满天星导航栏实现方法
2018/03/08 Javascript
微信小程序实现购物车代码实例详解
2019/08/29 Javascript
layer更改皮肤的实现方法
2019/09/11 Javascript
[02:57]DOTA2英雄基础教程 风行者
2014/01/16 DOTA
[10:39]DOTA2上海特级锦标赛音乐会纪录片
2016/03/21 DOTA
[01:02:05]LGD vs Mineski 2018国际邀请赛小组赛BO2 第一场 8.19
2018/08/21 DOTA
浅谈Python的文件类型
2016/05/30 Python
Python3学习笔记之列表方法示例详解
2017/10/06 Python
Python3 Random模块代码详解
2017/12/04 Python
pyQt4实现俄罗斯方块游戏
2018/06/26 Python
使用pytorch搭建AlexNet操作(微调预训练模型及手动搭建)
2020/01/18 Python
Pygame的程序开始示例代码
2020/05/07 Python
Keras设定GPU使用内存大小方式(Tensorflow backend)
2020/05/22 Python
Melijoe美国官网:法国奢侈童装购物网站
2017/04/19 全球购物
AJAX应用和传统Web应用有什么不同
2013/08/24 面试题
销售经理工作职责
2014/02/03 职场文书
委托协议书范本
2014/04/22 职场文书
联谊活动总结
2014/08/28 职场文书
警察群众路线整改措施
2014/09/26 职场文书
对党的十八届四中全会的期盼
2014/10/17 职场文书
学生考试舞弊检讨书
2015/01/01 职场文书
琅琊山导游词
2015/02/05 职场文书
2015年电信员工工作总结
2015/05/26 职场文书
2015中学学校工作总结
2015/07/20 职场文书
2019年怎样才能撰写出优秀的自荐信
2019/03/25 职场文书
银行求职信范文
2019/05/13 职场文书
2019餐饮行业创业计划书!
2019/06/27 职场文书
Java虚拟机内存结构及编码实战分享
2022/04/07 Java/Android