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 相关文章推荐
JDBC连接的六步实例代码(与mysql连接)
May 12 MySQL
Mysql中 unique列插入重复值该怎么解决呢
May 26 MySQL
MySQL系列之十五 MySQL常用配置和性能压力测试
Jul 02 MySQL
MySQL中utf8mb4排序规则示例
Aug 02 MySQL
mysql分表之后如何平滑上线详解
Nov 01 MySQL
MySQL派生表联表查询实战过程
Mar 20 MySQL
mysql中数据库覆盖导入的几种方式总结
Mar 25 MySQL
navicat 连接Ubuntu虚拟机的mysql的操作方法
Apr 02 MySQL
提高系统的吞吐量解决数据库重复写入问题
Apr 23 MySQL
详解Mysql事务并发(脏读、不可重复读、幻读)
Apr 29 MySQL
Mysql表数据比较大情况下修改添加字段的方法实例
Jun 28 MySQL
分享很少见很有用的SQL功能CORRESPONDING
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学习笔记(一):基本语法之标记、空白、和注释
2015/04/17 PHP
php线性表的入栈与出栈实例分析
2015/06/12 PHP
浅析PHP中Session可能会引起并发问题
2015/07/23 PHP
orm获取关联表里的属性值
2016/04/17 PHP
Yii2实现多域名跨域同步登录退出
2017/02/04 PHP
yii gridview实现时间段筛选功能
2017/08/15 PHP
Laravel 6 将新增为指定队列任务设置中间件的功能
2019/08/06 PHP
使用Mootools动态添加Css样式表代码,兼容各浏览器
2011/12/12 Javascript
圣诞节Merry Christmas给博客添加浪漫的下雪效果基于jquery实现
2012/12/27 Javascript
jquery中append()与appendto()用法分析
2014/11/14 Javascript
用javascript实现自动输出网页文本
2015/07/30 Javascript
jQuery Mobile和HTML5开发App推广注册页
2016/11/07 Javascript
如何提高javascript加载速度
2016/12/26 Javascript
JavaScript如何一次性展示几万条数据
2017/03/30 Javascript
JS实现table表格固定表头且表头随横向滚动而滚动
2017/10/26 Javascript
jqGrid表格底部汇总、合计行footerrow处理
2019/08/21 Javascript
Node.js安装详细步骤教程(Windows版)详解
2019/09/01 Javascript
Vue 禁用浏览器的前进后退操作
2020/09/04 Javascript
python 随机数使用方法,推导以及字符串,双色球小程序实例
2017/09/12 Python
python利用sklearn包编写决策树源代码
2017/12/21 Python
Python 解决OPEN读文件报错 ,路径以及r的问题
2019/12/19 Python
Python实现搜索算法的实例代码
2020/01/02 Python
pandas读取csv文件提示不存在的解决方法及原因分析
2020/04/21 Python
浅谈Pycharm的项目文件名是红色的原因及解决方式
2020/06/01 Python
python判断正负数方式
2020/06/03 Python
webView加载html图片遇到的问题解决
2019/10/08 HTML / CSS
美国网上购买眼镜:Eyeconic
2017/07/29 全球购物
如何在发生故障的节点上重新安装 SQL Server
2013/03/14 面试题
《母亲的恩情》教学反思
2014/02/13 职场文书
文秘大学生求职信
2014/02/25 职场文书
小学新学期寄语
2014/04/02 职场文书
学校端午节活动方案
2014/08/23 职场文书
语文教师个人工作总结
2015/02/06 职场文书
公司规章制度范本
2015/08/03 职场文书
带你彻底理解JavaScript中的原型对象
2021/04/14 Javascript
《雀魂PONG☆》4月1日播出 PV角色设定情报
2022/03/20 日漫