IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> 保姆级教程,终于搞懂脏读、幻读和不可重复读了!(经典回顾) -> 正文阅读

[大数据]保姆级教程,终于搞懂脏读、幻读和不可重复读了!(经典回顾)

1f409ce6274e22f5d92419a336e746df.png

作者 | 王磊

来源 | Java中文社群(ID:javacn666)

转载请联系授权(微信ID:GG_Stone)

我的文章合集:https://gitee.com/mydb/interview

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

  1. 读未提交(READ UNCOMMITTED)

  2. 读已提交(READ COMMITTED)

  3. 可重复读(REPEATABLE READ)

  4. 序列化(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 执行结果如下图所示:542ba68bb763d595734b68a3ff128834.png

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

select?@@global.transaction_isolation,@@transaction_isolation;

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

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

show?processlist;

以上 SQL 执行结果如下:9b3298a900f5262590261f0323994db6.png

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

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

show?status?like?'Threads%';

以上 SQL 执行结果如下:c6a4fa69e434839b02ac05f6e40d8085.png

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);

创建的表结构和数据如下:4a3b2a491be292034d51ad050f9877d8.png

8.名称约定

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

脏读

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

执行步骤客户端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

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

set?session?transaction?isolation?level?read?uncommitted;

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

然后使用命令来检查当前连接窗口的事务隔离界别,如下图所示:a0049fe098f3242e356cb8bf32b0dd6d.png开启事务并查询用户列表信息,如下图所示:0a4d65cb7b03e0150fc30f0a89259217.png

脏读演示步骤2

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

脏读演示步骤3

在窗口 2 中再次查询用户列表,执行结果如下:d76b91167292ab2eef13b61317680f26.png从上述结果可以看出,在窗口 2 中读取到了窗口 1 中事务未提交的数据,这就是脏读。

不可重复读

不可重复读是指一个事务先后执行同一条 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 同一个事务中的两次查询,得到了不同的结果这就是不可重复读,具体执行步骤如下。

不可重复读演示步骤1

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

set?session?transaction?isolation?level?read?committed;

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

使用命令来检查当前连接窗口的事务隔离界别,如下图所示:919da41d09b2c1cf1ca3b52c804d1f8e.png在窗口 2 中开启事务,并查询用户表,执行结果如下:c63f0261474af41aef86589e1a84e676.png此时查询的列表中,Java 用户的余额为 100 元。

不可重复读演示步骤2

在窗口 1 中开启事务,并给 Java 用户添加 20 元,但不提交事务,再观察窗口 2 中有没有脏读的问题,具体执行结果如下图所示:da619e8417d93cc30ab16364058a4c89.png从上述结果可以看出,当把窗口的事务隔离级别设置为读已提交,已经不存在脏读问题了。接下来在窗口 1 中提交事务,执行结果如下图所示:bc2b2323de45fa2f19db73fac1be589c.png

不可重复读演示步骤3

切换到窗口 2 中再次查询用户列表,执行结果如下:1ca18d808e991b324a83e14690e2910f.png从上述结果可以看出,此时 Java 用户的余额已经变成 120 元了。在同一个事务中,先后查询的两次结果不一致就是不可重复读。

不可重复读和脏读的区别

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

幻读

幻读名如其文,它就像发生了某种幻觉一样,在一个事务中明明没有查到主键为 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

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

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

以上 SQL 执行结果如下图所示:f56d3819e684aec4d8d2dc153195e07b.png从上述结果可以看出,查询的结果中 id=3 的数据为空。

幻读演示步骤2

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

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

以上 SQL 执行结果如下图所示:4c3da033d93c23ad9341f7cd6ae64b9b.png

幻读演示步骤3

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

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

以上 SQL 执行结果如下图所示:43aa40d59acfa1b964eb7cd374b51c82.png添加用户数据失败,提示表中已经存在了编号为 3 的数据,且此字段为主键,不能添加多个。

幻读演示步骤4

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

select?*?from?userinfo?where?id=3;

以上 SQL 执行结果如下图所示:01a29e506d0171b69741f867fba2a01c.png/ 在此事务中查询明明没有编号为 3 的用户,但插入的时候却却提示已经存在了,这就是幻读。

不可重复读和幻读的区别

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

总结

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

是非审之于己,毁誉听之于人,得失安之于数。?

公众号:Java面试真题解析

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2022-01-08 14:05:51  更:2022-01-08 14:05:57 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 13:51:19-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码