关于java:保姆级教程终于搞懂脏读幻读和不可重复读了

42次阅读

共计 4766 个字符,预计需要花费 12 分钟才能阅读完成。

我的文章合集: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 执行后果如下图所示:

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

select @@global.transaction_isolation,@@transaction_isolation;

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

每个 MySQL 命令行窗口就是一个 MySQL 客户端,每个客户端都能够独自设置(不同的)事务隔离级别,这也是演示 MySQL 并发事务的根底。以下是查问客户端连贯的 SQL 命令:

show processlist;

以上 SQL 执行后果如下:

5. 查问连贯客户端的数量

能够应用以下 SQL 命令,查问连以后接 MySQL 服务器的客户端数量:

show status like 'Threads%';

以上 SQL 执行后果如下:

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

创立的表构造和数据如下:

8. 名称约定

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

脏读

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

执行步骤 客户端 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:事务隔离级别读未提交存在脏读的问题。

而后应用命令来查看以后连贯窗口的事务隔离界别,如下图所示:

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

脏读演示步骤 2

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

脏读演示步骤 3

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

从上述后果能够看出,在窗口 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:读已提交能够解决脏读的问题,但存在不可反复读的问题。

应用命令来查看以后连贯窗口的事务隔离界别,如下图所示:

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

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

不可反复读演示步骤 2

在窗口 1 中开启事务,并给 Java 用户增加 20 元,但不提交事务,再察看窗口 2 中有没有脏读的问题,具体执行后果如下图所示:

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

不可反复读演示步骤 3

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

从上述后果能够看出,此时 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 执行后果如下图所示:

从上述后果能够看出,查问的后果中 id=3 的数据为空。

幻读演示步骤 2

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

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

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

幻读演示步骤 3

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

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

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

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

幻读演示步骤 4

在窗口 2 中,从新执行查问:

select * from userinfo where id=3;

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

不可反复读和幻读的区别

二者形容的则重点不同,不可反复读形容的侧重点是批改操作,而幻读形容的侧重点是增加和删除操作。

总结

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

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

公众号:Java 面试真题解析

正文完
 0