一文搞懂MySQL脏读,幻读和不可重复读

目录
  • MySQL 中事务的隔离
    • 1.READ UNCOMMITTED
    • 2.READ COMMITTED
    • 3.REPEATABLE READ
    • 4.SERIALIZABLE
  • 前置知识
    • 1.事务相关的常用命令
    • 2.MySQL 8 之前查询事务的隔离级别
    • 3.MySQL 8 之后查询事务的隔离级别
    • 4.查看连接的客户端详情
    • 5.查询连接客户端的数量
    • 6.设置客户端的事务隔离级别
    • 7.新建数据库和测试数据
    • 8.名称约定
  • 脏读
    • 1.脏读演示步骤1
    • 2.脏读演示步骤2
    • 3.脏读演示步骤3
    • 4.不可重复读
    • 5.不可重复读演示步骤1
    • 6.不可重复读演示步骤2
    • 7.不可重复读演示步骤3
    • 8.不可重复读和脏读的区别
  • 幻读
    • 1.幻读演示步骤1
    • 2.幻读演示步骤2
    • 3.幻读演示步骤3
    • 4.幻读演示步骤4
    • 5.不可重复读和幻读的区别
  • 总结

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 执行结果如下图所示: 

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.脏读演示步骤1

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

set session transaction isolation level read uncommitted;

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

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

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

2.脏读演示步骤2

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

3.脏读演示步骤3

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

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

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

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

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

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

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

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

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

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

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

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

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

2.幻读演示步骤2

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

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

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

3.幻读演示步骤3

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

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

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

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

4.幻读演示步骤4

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

select * from userinfo where id=3;

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

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

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

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

总结

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

到此这篇关于一文搞懂MySQL脏读,幻读和不可重复读的文章就介绍到这了,更多相关MySQL脏读内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 基于Java实现Avro文件读写功能

    目录 模式(schema) 与其他系统的比较 Java客户端实现 定义一个schema 使用Java代码生成插件生成的User类进行序列化和反序列化 在不生成User类的情况下直接进行序列化和反序列化操作 总结 Apache Avro是一个数据序列化系统.具有如下基本特性: 丰富的数据结构.一种紧凑.快速的二进制数据格式.一个容器文件,用于存储持久数据.远程过程调用 (RPC).与动态语言的简单集成. 代码生成不需要读取或写入数据文件,也不需要使用或实现 RPC 协议. 代码生成作为一种可选的优

  • java读取配置文件(properties)的时候,unicode码转utf-8方式

    目录 java读取properties,unicode码转utf-8 代码如下 properties配置文件编码问题 java读取properties,unicode码转utf-8 有时我们在读取properties结尾的配置文件的时候,如果配置文件中有中文,那么我们读取到的是unicode码的中文,需要我们在转换一下 代码如下 /** * 将配置文件中的Unicode 转 utf-8 汉字 * @param 原始字符串 * @return 转换后的格式的字符串 */ public static

  • Java实现读取163邮箱,qq邮箱的邮件内容

    通过使用java mail来实现读取163邮箱,qq邮箱的邮件内容. 1.代码实现 创建springboot项目,引入依赖包 <!--mail--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-mail</artifactId> </dependency> 实现类 import com.su

  • java io文件操作从文件读取数据的六种方法

    目录 1.Scanner 2.Files.lines (Java 8) 3.Files.readAllLines 4.Files.readString(JDK 11) 5.Files.readAllBytes() 6.经典管道流的方式 在上一篇文章中,我为大家介绍了<5种创建文件并写入文件数据的方法>,本节我们为大家来介绍6种从文件中读取数据的方法. 另外为了方便大家理解,我为这一篇文章录制了对应的视频:总结java从文件中读取数据的6种方法-JAVA IO基础总结第二篇 Scanner(Ja

  • Java 使用geotools读取tiff数据的示例代码

    Java中如果要解析tiff,其实很多时候,我们都选择gdal的Java库来实现,毫无疑问,gdal确实在GIS数据处理方面非常的强悍,其实Geotools中很多有关栅格数据的解析,也是基于Gdal进行的封装,今天就简单了解使用GeoTools来解析Tiff数据. package com.dudu.gis; import org.geotools.gce.geotiff.GeoTiffReader; import org.geotools.geometry.GeneralEnvelope; im

  • Java实现读取resources目录下的文件路径的九种方式

    目录 前情提要 方式一 方式二 方式三 方式四(重要) 方式五(重要) 方式六(重要) 方式七 方式八 方式九 前情提要 本文中提供了九种方式获取resources目录下文件的方式.其中打印文件的方法如下:     /**      * 根据文件路径读取文件内容      *      * @param fileInPath      * @throws IOException      */     public static void getFileContent(Object fileIn

  • 一文搞懂MySQL脏读,幻读和不可重复读

    目录 MySQL 中事务的隔离 1.READ UNCOMMITTED 2.READ COMMITTED 3.REPEATABLE READ 4.SERIALIZABLE 前置知识 1.事务相关的常用命令 2.MySQL 8 之前查询事务的隔离级别 3.MySQL 8 之后查询事务的隔离级别 4.查看连接的客户端详情 5.查询连接客户端的数量 6.设置客户端的事务隔离级别 7.新建数据库和测试数据 8.名称约定 脏读 1.脏读演示步骤1 2.脏读演示步骤2 3.脏读演示步骤3 4.不可重复读 5.

  • Mysql数据库事务的脏读幻读及不可重复读详解

    目录 一.什么是数据库事务 二.事务的ACID原则 1. 原子性(Atomicity) 2. 一致性(Consistency) 3. 持久性(Durability) 4. 隔离性(Isolation) 三.隔离带来的问题 1. 脏读 2. 不可重复读 3.幻读 四.手动测试下事务的过程 一.什么是数据库事务 数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位.事务由事务开始与事务结束之间执行的全部数

  • 一文搞懂Mysql中的共享锁、排他锁、悲观锁、乐观锁及使用场景

    目录 一.常见锁类型 二.Mysql引擎介绍 三.常用引擎间的区别 四.共享锁与排他锁 五.排他锁的实际应用 六.共享锁的实际应用 七.死锁的发生 八.另一种发生死锁的情景 九.死锁的解决方式 十.意向锁和计划锁 十一.乐观锁和悲观锁 总结 一.常见锁类型 表级锁,锁定整张表 页级锁,锁定一页 行级锁,锁定一行 共享锁,也叫S锁,在MyISAM中也叫读锁 排他锁,也叫X锁,在MyISAM中也叫写锁 悲观锁,抽象性质,其实不真实存在 乐观锁,抽象性质,其实不真实存在 常见锁类型 二.Mysql引擎

  • MySQL脏读幻读不可重复读及事务的隔离级别和MVCC、LBCC实现

    目录 前言 事务因并发出现的问题有哪些 脏读 不可重复读 幻读 不可重复读与幻读的区别 事务的四个隔离级别 InnoDB默认的隔离级别是RR Read UnCommited 读未提交 RU Read Commited 读已提交 RC Repeatable Read 可重复读 RR Serializable 串行化 undo 版本链 read view MVCC(Multi-Version Concurrent Control )多版本并发控制 可重复读实现 读已提交实现 LBCC 锁的类型 共享

  • 一文搞懂MySQL元数据锁(MDL)

    目录 一.什么是metadata lock 二.MDL和行锁有什么区别 三.MDL为什么会造成系统崩溃 四.MDL的生命周期有多长 五.如何快速找到阻塞源头 六.本文开始的案例最终如何解决 小结 某日,路上收到用户咨询,为了清除空间,想删除某200多G大表数据,且已经确认此表不再有业务访问,于是执行了一条命令‘delete from bigtable’,但好长时间也没删完,经过咨询后,获知drop table删除表速度快,而且能彻底释放空间,于是又在另外一个session中执行了‘drop ta

  • 一文搞懂MySQL预编译

    1.预编译的好处 大家平时都使用过JDBC中的PreparedStatement接口,它有预编译功能.什么是预编译功能呢?它有什么好处呢? 当客户发送一条SQL语句给服务器后,服务器总是需要校验SQL语句的语法格式是否正确,然后把SQL语句编译成可执行的函数,最后才是执行SQL语句.其中校验语法,和编译所花的时间可能比执行SQL语句花的时间还要多. 如果我们需要执行多次insert语句,但只是每次插入的值不同,MySQL服务器也是需要每次都去校验SQL语句的语法格式,以及编译,这就浪费了太多的时

  • 一文搞懂MySQL XA如何实现分布式事务

    目录 前言 XA 协议 如何通过MySQL XA实现分布式事务 前言 MySQL支持单机事务的良好表现毋庸置疑,那么在分布式系统中,涉及多个节点,MySQL又是如何实现分布式事务的呢?比如开发一个业务系统,它接受外部的请求,然后访问多个内部其它系统才能执行该请求.执行时我们需要同时更新多个数据库的值(D1,D2,D3).由于系统必须处于一个一致性,也就是这三个数据库的值要么同时更新成功,要么全部不更新.不然会造成子系统有些指令成功了,有些指令尚未执行.导致对结果理解混乱. 那么,MySQL如何实

  • 一文搞懂MySQL持久化和回滚的原理

    目录 redo log 为什么要先更新内存数据,不直接更新磁盘数据? 为什么需要redo log? redo log是如何实现的? 为什么一个block设计成512字节? 为什么要两段式提交? crash后是如何恢复的? undo log 什么情况下会生成undo log? undo log是如何回滚的? undo log存在什么地方? redo log 事务的支持是数据库区分文件系统的重要特征之一,事务的四大特性: 原子性:所有的操作要么都做,要么都不做,不可分割. 一致性:数据库从一种状态变

  • 一文搞懂MySQL运行机制原理

    目录 前言 MySQL服务器体系架构 网络连接层 服务层 存储引擎层 系统文件层 服务器处理客户端请求 连接管理 解析与优化 查询缓存 语法解析 查询优化 存储引擎 小结 前言 前文我们了解了MySQL采用客户端/服务器架构,用户通过客户端程序发送增删改查需求,服务器程序收到请求后处理,并且把处理结果返回给客户端.这篇文章主要看下MySQL服务端是如何处理客户端的请求,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助. MySQL服务器体系架构 MySQL Server架构自顶向下大致可以分网

  • 一文搞懂MySQL索引页结构

    目录 1.前言 2.索引页结构 2.1FileHeader 2.2PageHeader 2.3UserRecords 2.4Infimum&Supremum 2.5PageDirectory 2.6FileTrailer 3.总结 1. 前言 「页」是InnoDB管理存储空间的基本单位,也是内存和磁盘交互的基本单位.也就是说,哪怕你需要1字节的数据,InnoDB也会读取整个页的数据,下次读取的数据如果恰巧也在这个页里,就能命中缓存了.写也是一样的,写数据前要先把页加载到内存,然后在内存中修改,该

随机推荐