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 05 MySQL
一看就懂的MySQL的聚簇索引及聚簇索引是如何长高的
May 25 MySQL
Mysql 如何实现多张无关联表查询数据并分页
Jun 05 MySQL
MySQL系列之八 MySQL服务器变量
Jul 02 MySQL
MySQL系列之十四 MySQL的高可用实现
Jul 02 MySQL
QT连接MYSQL数据库的详细步骤
Jul 07 MySQL
Mysql中一千万条数据怎么快速查询
Dec 06 MySQL
Windows下载并安装MySQL8.0.x 版本的完整教程
Apr 10 MySQL
Mysql 8.x 创建用户以及授予权限的操作记录
Apr 18 MySQL
MySQL串行化隔离级别(间隙锁实现)
Jun 16 MySQL
MySQL生成千万测试数据以及遇到的问题
Aug 05 MySQL
Mysql中mvcc各场景理解应用
Aug 05 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开启gzip页面压缩实例
2013/06/09 PHP
改写函数实现PHP二维/三维数组转字符串
2013/09/13 PHP
php创建session的方法实例详解
2015/01/27 PHP
PHP获得数组交集与差集的方法
2015/06/10 PHP
Thinkphp事务操作实例(推荐)
2017/04/01 PHP
HR vs CL BO3 第二场 2.13
2021/03/10 DOTA
jquery 必填项判断表单是否为空的方法
2008/09/14 Javascript
20个非常棒的 jQuery 幻灯片插件和教程分享
2011/08/23 Javascript
js中prototype用法详细介绍
2013/11/14 Javascript
有关Promises异步问题详解
2015/11/13 Javascript
详解jQuery简单的表单应用
2016/12/16 Javascript
Node.js静态服务器的实现方法
2018/02/28 Javascript
使用vue开发移动端管理后台的注意事项
2019/03/07 Javascript
解决Vue 刷新页面导航显示高亮位置不对问题
2019/12/25 Javascript
Vue中axios拦截器如何单独配置token
2019/12/27 Javascript
React学习之JSX与react事件实例分析
2020/01/06 Javascript
JS实现音量控制拖动
2020/01/15 Javascript
[58:37]Serenity vs Fnatic 2018国际邀请赛淘汰赛BO1 8.21
2018/08/22 DOTA
python中对list去重的多种方法
2014/09/18 Python
python返回昨天日期的方法
2015/05/13 Python
Python对象转JSON字符串的方法
2016/04/27 Python
requests和lxml实现爬虫的方法
2017/06/11 Python
Python操作MySQL数据库的方法
2018/06/20 Python
python 计算两个列表的相关系数的实现
2019/08/29 Python
Python调用.NET库的方法步骤
2019/12/27 Python
Python Selenium实现无可视化界面过程解析
2020/08/25 Python
Aeropostale官网:美国著名校园品牌及青少年服饰品牌
2019/03/21 全球购物
应届生会计求职信
2013/11/11 职场文书
大学生职业生涯规划书模版
2013/12/30 职场文书
幼儿园教师师德师风演讲稿:我自豪我是一名幼师
2014/09/10 职场文书
上诉状格式
2015/05/23 职场文书
金正昆讲礼仪观后感
2015/06/11 职场文书
2016形势与政策学习心得体会
2016/01/12 职场文书
Python基础之元类详解
2021/04/29 Python
微软团队与 NASA 科学家和惠普企业(HPE)的工程师合作
2022/04/21 数码科技
python神经网络 使用Keras构建RNN训练
2022/05/04 Python