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

article/2025/10/3 11:03:14

目录

  • 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.事务相关的常用命令

1

2

3

4

5

6

7

8

# 查看 MySQL 版本

select version();

# 开启事务

start transaction;

# 提交事务

commit;

# 回滚事务

rollback;

2.MySQL 8 之前查询事务的隔离级别

查看全局 MySQL 事务隔离级别和当前会话的事务隔离级别的 SQL 如下:

1

select @@global.tx_isolation,@@tx_isolation;

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

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

1

select @@global.transaction_isolation,@@transaction_isolation;

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

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

1

show processlist;

以上 SQL 执行结果如下: 

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

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

1

show status like 'Threads%';

以上 SQL 执行结果如下: 

 6.设置客户端的事务隔离级别

通过以下 SQL 可以设置当前客户端的事务隔离级别:

1

set session transaction isolation level 事务隔离级别;

事务隔离级别的值有 4 个:READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE。

7.新建数据库和测试数据

创建测试数据库和表信息,执行 SQL 如下:

1

2

3

4

5

6

7

8

9

10

11

12

-- 创建数据库

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 的事务隔离级别为读未提交,设置命令如下:

1

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 的事务隔离级别为读已提交,设置命令如下:

1

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 如下:

1

2

3

set session transaction isolation level repeatable read;

start transaction;

select * from userinfo where id=3;

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

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

2.幻读演示步骤2

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

1

2

3

start transaction;

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

commit;

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

 

3.幻读演示步骤3

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

1

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

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

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

4.幻读演示步骤4

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

1

select * from userinfo where id=3;

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

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

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

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

总结

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

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


http://chatgpt.dhexx.cn/article/aKfikpPy.shtml

相关文章

脏读、幻读和不可重复读

一、引言 脏读、不可重复读和幻读是数据库中由于并发访问导致的数据读取问题。当多个事务同时进行时可以通过修改数据库事务的隔离级别来处理这三个问题。 二、问题解释 1、脏读(读取未提交的数据) 脏读又称无效数据的读出,是指在数据库访…

一文详解脏读、不可重复读、幻读

MySQL 是支持多事务并发执行的。否则来一个事务处理一个请求,处理一个人请求的时候,其它事务都等着,那估计都没人敢用MySQL作为数据库,因为用户体验太差,估计都要砸键盘了。 既然事务可以并发操作,这里就有一些问题:一…

快速理解脏读、不可重复读和幻读

MySQL的InnoDB引擎是支持事务的,但是并发事务的处理又会带来以下问题: 脏读不可重复读幻读 一、脏读 脏读指事务A读取到了事务B更新了但是未提交的数据,然后事务B由于某种错误发生回滚,那么事务A读取到的就是脏数据。 具体的说…

引用及指针和引用的区别

一.在C语言中,我们要给函数传参,有两种方法。 1.传值 void Swap(int a,int b) {int tmpa;ab;btmp; } int main() {int a10;int b20;Swap(a,b);return 0; }优点:形参不影响实参(保护实参),可读性强。 缺点…

C++ | 指针和引用的区别

01. C——指针和引用的区别 指针是一个存储变量地址的变量,指向内存的一个存储单元。 引用只是一个别名。 int a1; int *p&a;int a1; int &ba;使用sizeof看一个指针的大小是8,而引用则是被引用对象的大小。指针可以被初始化为NULL,…

C++—指针与引用的区别与联系

一、为什么要有引用(C语言没有) ▪ 因为引⽤和值有⼀样的语义,而指针不是 ▪ 不存在空引⽤,必须初始化;保证值不变,保证编译器更加安全 ▪ 加减号、赋值操作符,作⽤在引用上会触发对象的操作符重…

C++指针与引用的区别

指针和引用的区别 ①指针是一个变量,存储一个成员的地址;引用是一个常量(指针常量),相当于一个成员的别名 ②指针声明和定义可以分开;引用声明时必须初始化 int* a;//指针声明 anew int(1);//指针定义 int…

【C++】---指针和引用的区别

指针和引用的区别 两者本质两者区别两者的相同点为什么传引用比传指针更安全两者做返回值效率的比较 两者本质 引用是别名,指针是地址、实体 两者区别 不同点分析1.初始化要求不同引用在创建的同时必须初始化,即引用到一个有效的对象,而指…

【C++】指针和引用的区别及指针传递和引用传递的区别

一、指针和引用的区别: 1.指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;引用和原来的变量实质上是同一个东西,只不过是原变量的一个别名。 2.指针的值在初始化后可变,即指向其…

指针和引用的区别以及引用与指针基础

1引用: 引用(reference)为对象起了另外一个新的名字通过将声明符写成&d的形式来定义引用类型,其中d是声明的变量名: int ival 1024;int& refval ival;int& refval2;//报错:引用必须被初始化…

C++中指针和引用的区别(超详细)

指针和引用主要有以下区别: 引用必须被初始化,但是不分配存储空间。指针不声明时初始化,在初始化的时候需要分配存储空间。 引用初始化后不能被改变,指针可以改变所指的对象。 不存在指向空值的引用,但是存在指向空值的…

c++:指针和引用的区别

目录 前言: 1、引用概念上是定义一个变量的别名,而指针是存储一个变量的地址。 2、引用在定义时必须要初始化,但是指针没有要求。 3、引用在初始化时引用一个实体后,就不能再引用其他实体,因为其本质是一个指针常量…

C++中指针和引用的区别

1.指针和引用的定义和性质区别: (1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来 的变量实质上是同一个东西,只不过是原变量的一个别…

C++中的指针与引用

写在前面 指针和引用形式上很好区别,但是他们似乎有相同的功能,都能够直接引用对象,对其进行直接的操作。但是什么时候使用指针?什么时候使用引用呢?这两者很容易混淆,在此我详细介绍一下指针和引用,力争将最真实的一…

引用和指针概念及区别

一、引用和指针 指针:指针是一个特殊的变量,它里面存储的的数值为内存里的一个地址,通过*访问内存地址所指向的值 引用:引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内…

01背包.动态规划.c语言实现

二维dp数组01背包 确定dp数组以及下标的含义 使用二维数组,即dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少 2.递推公式:dp[i][j]dp[i-1][j-weight]value[i] dp[i][j]dp[i-1][j] 3.数组初始…

【动态规划】背包问题

注意:for j in range(1,m1):是枚举所有的情况,不用一一判断放入物品后背包容量减少后j的变化。为什么从1开始,因为0已经写出来了,即dp[i-1][j]dp[i-1][j-0*a[i-1]]0*v[i-1]。 01背包无价值 dp定义:前i项物…

动态规划——背包问题(01背包问题)

动态规划——背包问题(01背包问题)01背包问题(求最大价值):问题优化01背包问题(求方案数): 动态规划——背包问题(01背包问题) 01背包问题(求最大价值): 有N件物品和一个最多能背重量为W的背包…

动态规划:关于01背包问题 I

动态规划:关于01背包问题,你该了解这些! 对于面试的话,其实掌握01背包,和完全背包,就够用了,最多可以再来一个多重背包。 如果这几种背包,分不清,我这里画了一个图&…

c++ 动态规划-01背包

动态规划 - 01背包问题 1.使用递归遍历(穷举)求解: 01背包问题:给定 n 种物品和一个重量(容量)(限定条件)为 w 的背包,物品 i 的重量是 wi,其价值为 vi。(每种物品只有一个)问:如何选择装入背包的物品,使得装入背包中的物品的价值最大。 //VC6.0------…