【MySQL】事务

目录

[1. 什么是事务?](#1. 什么是事务?)

[1.1 定义与解释](#1.1 定义与解释)

[1.2 事务的ACID特性](#1.2 事务的ACID特性)

[2. 为什么要使用事务?](#2. 为什么要使用事务?)

[3. 事务的使用](#3. 事务的使用)

[3.1 查看支持事务的存储引擎](#3.1 查看支持事务的存储引擎)

[3.2 开启事务、提交事务、回滚事务](#3.2 开启事务、提交事务、回滚事务)

[a. 开启一个事务,执行后回滚](#a. 开启一个事务,执行后回滚)

[b. 开启一个事务,执行后提交](#b. 开启一个事务,执行后提交)

[c. 保存点](#c. 保存点)

[3.3 自动/手动提交每条SQL语句的事务](#3.3 自动/手动提交每条SQL语句的事务)

[4. 事务的隔离性和隔离级别](#4. 事务的隔离性和隔离级别)

[4.1 什么是隔离性与隔离级别](#4.1 什么是隔离性与隔离级别)

[4.2 查看隔离级别](#4.2 查看隔离级别)

[4.3 设置隔离级别](#4.3 设置隔离级别)

[5. 不同隔离级别之间存在的问题](#5. 不同隔离级别之间存在的问题)

[5.1 读未提交 --> 脏读](#5.1 读未提交 --> 脏读)

[5.2 读已提交 --> 不可重复读](#5.2 读已提交 --> 不可重复读)

[5.3 可重复读 --> 幻读](#5.3 可重复读 --> 幻读)

[5.4 串行化](#5.4 串行化)

[5.5 易混淆点解析](#5.5 易混淆点解析)


1. 什么是事务?

1.1 定义与解释

MySQL 事务(Transaction)是一组不可分割的数据库操作序列,这些操作要么全部执行成功,要么全部执行失败,最终使数据库从一个一致性状态切换到另一个一致性状态。


假设你要从账户A转账100元到账户B,这需要两步操作:

  1. UPDATE 账户A的新余额 = 原余额1000 - 100 = 900

  2. UPDATE 账户B的新余额 = 原余额1000 + 100 = 1100

转账前A和B的总余额是1000+1000=2000,转账后A和B的总余额是900+1100=2000,也就是说总余额在转账前后的状态是一致的。(两步都执行成功)

在此过程中如果账户A被扣了100元,但是账户B并没有收到这100块,说明转账这一事物并没有完成或者发生了异常,事物必须回滚,把这一百块钱退还给账户A。(第2步执行失败,两步都执行失败)

如果没有事务,可能会出现的问题:

  • 第一步执行成功,账户A扣了100元。

  • 第二步执行前,数据库崩溃了,或者程序出错了。

  • 结果:账户A的钱没了,账户B的钱没到账。总余额严重不一致!

使用事务就可以保证:

  • 两步操作被"打包"成一个事务。

  • 如果任何一步失败,整个事务会回滚,账户A的扣款操作也会被撤销,就像什么都没发生过一样。

  • 只有两步都成功,事务才会提交,更改才永久生效。

1.2 事务的ACID特性

事务的核心特性是ACID,这是数据库事务必须遵循的四大基本准则,确保了事务执行的可靠性和数据的一致性,具体如下:

特性 解释 转账例子中的体现
A tomicity 原子性 事务是最小单元,不可再分割。⼀个事务中的所有操作,要么全部成功,要么全部失败回滚 扣款和加款必须同时成功或同时失败,不能只完成一半。
C onsistency 一致性 事务使数据库从一个一致状态 转变到另一个一致状态,不会破坏数据完整性。 转账前后,两个账户的总金额保持不变(符合++业务逻辑和规则++)。
I solation 隔离性 多个并发事务之间相互隔离,互不干扰。这是通过事务隔离级别来控制的。 在你转账过程中,另一个查询事务不会看到你中途扣了款但还没加款的"中间状态"。
D urability 持久性 事务一旦提交其对数据的修改就是永久性的,即使系统故障也不会丢失。 转账成功后,即使数据库服务器立刻断电,重启后账户余额也是转账后的结果。

2. 为什么要使用事务?

⽀持事务的数据库能够简化我们的编程模型,不需要我们去考虑各种各样的潜在错误和并发问题 。在使⽤事务过程中,要么提交,要么回滚,不用去考虑网络异常、服务器宕机等其他因素,因此 我们经常接触的事务本质上是数据库对 ACID 模型的⼀个实现,是为应⽤层服务的。

事务的本质是一种契约:

对开发者的承诺:"你只需关注业务逻辑,我来保证数据安全"。

对业务的承诺:"要么完全成功,要么完全失败,不会有中间状态"。

对系统的承诺:"故障恢复后,数据依然一致"。

3. 事务的使用

3.1 查看支持事务的存储引擎

要使⽤事务那么数据库就要⽀持事务,在MySQL中⽀持事务的存储引擎是InnoDB,可以通过 show engines 语句查看:

可以看到InnoDB是默认支持事务的。

3.2 开启事务、提交事务、回滚事务

开始⼀个新的事务

方式一:start transaction;

方式二:begin;

提交当前事务,造成永久性更改

commit;

回滚当前事务,取消其更改

rollback;

begin关键字是MySQL特有的,start关键字符合SQL标准。


在演示示例前,要准备好要用的表和数据:

sql 复制代码
 CREATE TABLE `bank_account`  (
 `id` bigint PRIMARY KEY AUTO_INCREMENT, 
 `name` varchar(255) NOT NULL,     # 姓名
 `balance` decimal(10, 2) NOT NULL # 余额
);
 INSERT INTO bank_account(`name`, balance) VALUES('张三', 1000);
 INSERT INTO bank_account(`name`, balance) VALUES('李四', 1000);
 SELECT * FROM bank_account;

a. 开启一个事务,执行后回滚

-- 张三给李四转账100块钱,完成后回滚到转账前

sql 复制代码
​
start transaction;
  update bank_account set balance = balance - 100 where name = '张三';
  # 在事物执行中途查看变化
  SELECT * FROM bank_account;
  update bank_account set balance = balance + 100 where name = '李四';
  # 在事务的结尾查看结果
  SELECT * FROM bank_account; 
rollback;
  # 回滚后查看结果
  SELECT * FROM bank_account;  

​

中间状态:张三有900,李四有1000

在事物的结尾看到的结果:张三有900,李四有1100【满足一致性】

回滚到事物开始前的状态:张三有1000,李四有1000【满足一致性】

b. 开启一个事务,执行后提交

-- 张三给李四转账100块钱,完成后提交这个事物

sql 复制代码
begin;
  update bank_account set balance = balance - 100 where name = '张三';
  SELECT * FROM bank_account;  # 在事务执行过程中查看结果
  update bank_account set balance = balance + 100 where name = '李四';
  SELECT * FROM bank_account;  # 在事务的结尾查看结果
commit;  
  SELECT * FROM bank_account;  # 提交后查看结果

中间状态:张三有900,李四有1000

在事物的结尾看到的结果:张三有900,李四有1100【满足一致性】

提交了事物,正式完成了修改:张三有900,李四有1100【满足一致性】

c. 保存点

在事务执⾏的过程中设置保存点,回滚时指定保存点可以把数据恢复到保存点的状态:

语法:

savepoint 保存点名称;

rollback to 保存点名称;

  • 一个事物中可以设置多个保存点,方便测试人员的调试。

-- 示例:一个事物中按顺序完成"张三转账给李四100块"、"张三再次转账100块"、"插入一个新账户王五"

sql 复制代码
START TRANSACTION;  
  # a.张三给李四转100块
  SELECT * FROM bank_account;
  update bank_account set balance = balance - 100 where name = '张三';
  update bank_account set balance = balance + 100 where name = '李四';
  SELECT * FROM bank_account;
 SAVEPOINT sp1;
  # b.张三再给李四转100块
  update bank_account set balance = balance - 100 where name = '张三';
  update bank_account set balance = balance + 100 where name = '李四';
  SELECT * FROM bank_account;
 savepoint sp2;  
  # c.插入一个账户王五
  insert into bank_account values(3,'王五',5000);
  SELECT * FROM bank_account; 
 rollback to sp2;  # 1.回滚到sp2记录点
  SELECT * FROM bank_account;
 rollback to sp1;  # 2.回滚到sp1记录点
  SELECT * FROM bank_account;  
ROLLBACK;          # 3.回滚到start之前  
  SELECT * FROM bank_account;

a操作

a操作之前:张三900,李四1100

a操作之后(b操作之前):张三800,李四1200

b操作

b操作之后(c操作之前):张三700,李四1300

c操作

b操作之后:张三700,李四1300,王五5000

  1. 回滚到保存点sp2:张三700,李四1300
  1. 回滚到保存点sp1:张三800,李四1200
  1. 回滚到start之前:张三900,李四1100

3.3 自动/手动提交每条SQL语句的事务

其实,我们平时执行的每一条SQL语句都是一个微小的事务,但是我们并没有手动提交事物,这是因为MySQL默认是自动提交事务的。

查看当前事务是否⾃动提交,可以使⽤以下语句:

show variables like 'autocommit';

"on"表示数据库此时是自动提交事物的。


可以通过以下语句修改事务为自动或⼿动提交

设置事务为⾃动提交

方式1:SET AUTOCOMMIT=1;

方式2:SET AUTOCOMMIT=ON;

设置事务为⼿动提交

方式1:SET AUTOCOMMIT=0;

方式2:SET AUTOCOMMIT=OFF;
注意事项:

  • 手动提交模式下,单条SQL语句会自动开启事物,不需要显式使用 start;但不会自动提交,需要显式使用 commit。
  • 只要显式使用 start 或 begin 开启的事物,都必须通过通过 commit 提交才能持久化保存。【这与自动/手动模式无关,与是否显式开启有关。】

4. 事务的隔离性和隔离级别

4.1 什么是隔离性与隔离级别

同一个MySQL服务可以同时被多个客户端访问,每个客户端执行的DML语句是以事务为基本单位的。

假设事物没有隔离性,那么不同的客户端 在对(同⼀张表中的)同⼀条数据进行修改的时候,就可能出现相互影响的情况。

为了保证不同的事务之间在执行的过程中不受影响,那么事务之间就需要相互隔离,这种特性就是隔离性。


事务间不同程度的隔离,称为事务的隔离级别。

不同的隔离级别在性能和安全⽅⾯做了取舍,有 的隔离级别注重并发性,有的注重安全性,有的则是并发和安全适中;在MySQL的InnoDB引擎中事务 的隔离级别有四种,分别是:

  • read uncommitted:读未提交
  • read committed:读已提交
  • repeatable read:可重复读(默认)
  • serializable:串行化

其中,"可重复读"是MySQL的InnoDB引擎中默认的隔离级别。

【MyISAM不支持事务,所有语句都自动提交,不可回滚,也就没有隔离级别的说法】

4.2 查看隔离级别

事务的隔离级别分为全局作用域会话作用域

维度 全局作用域 会话作用域
生效范围 数据库实例级,影响所有后续新建会话 单个会话级,仅影响当前连接,独立于全局范围
生效时机 修改后,仅新创建的会话生效,已有会话不受影响 修改后立即生效,当前会话后续事务直接使用
生命周期 仅在数据库实例运行期间有效,重启后恢复配置文件默认值(如 my.cnf) 随会话断开(连接关闭)立即失效,重新连接后继承当前全局值

查看不同作⽤域事务的隔离级别,可以使⽤以下的方式:

# 查看全局作用域下的隔离级别:

SELECT @@GLOBAL.transaction_isolation;

# 查看会话作用域下的隔离级别:
SELECT @@SESSION.transaction_isolation;

  • 在 MySQL 中,@@ 是引用系统变量的标识前缀,用来获取或设置 MySQL 内置的系统参数值。

InnoDB 默认的隔离级别是可重复读:

4.3 设置隔离级别

设置事务的隔离级别[或访问模式],可以使⽤以下语法:

方式一:

set [ golbal | session ] transaction isolation level [ 隔离级别 | 访问模式 ];

  • 通过 GLOBAL 和 SESSION 分别指定不同作⽤域的事务隔离级别。如果不填写,则默认是回话作用域
  • 可填写的隔离级别有4种:
    • READ UNCOMMITTED # 读未提交
    • READ COMMITTED # 读已提交
    • REPEATABLE READ # 可重复读
    • SERIALIZABLE # 串行化
  • 可填写的访问模式有2种:
    • READ WRITE # 表示事务可以对数据进行读写
    • READ ONLY # 表示事务是只读,不能对数据进行读写

方式二:

修改隔离级别:

set [ golbal | session ] transaction_isolation = 隔离级别;

修改访问模式:

set [ golbal | session ] transaction_read_only = 访问模式;

  • 修改隔离级别要用transaction_isolation ,修改访问模式用transaction_read_only。
  • 等号右边的取值如下:(通常是字符串,注意横杠)
    • 隔离级别:

      • 'READ-UNCOMMITTED'

      • 'READ-COMMITTED'

      • 'REPEATABLE-READ'

      • 'SERIALIZABLE'

    • 访问模式:(没有像 'READ WRITE' 、'READ-ONLY'这样的取值)

      • 1 或 'OFF' # 表示读写

      • 0 或 'ON' # 表示只读

方式三:

修改隔离级别:

例如:SET @@SESSION.transaction_isolation = 隔离级别;

修改访问模式:

例如:SET @@SESSION.transaction_read_only = 访问模式;

  • 用@@引用变量时,要注意**"作用域" 与 "系统变量名" 之间有一个** 小点 .
  • 等号右边可填入的值与方式二相同

隔离级别与访问模式的区别:

维度 事务隔离级别 事务访问模式
管控目标 事务之间的并发数据可见性(如避免脏读、不可重复读) 事务自身的操作权限范围(能否执行写操作)
核心作用 保证并发场景下的数据一致性 限制事务的操作类型,防止误写 / 控制资源消耗

-- 示例:演示两个客户端在其中一个客户端 修改回话作用域的隔离级别后,两者隔离级别的变化

  1. 修改前:

a. 全局的隔离级别:可重复读

b. 将被修改隔离级别的客户端:

该用户的回话作用域:可重复读

c. 其他客户端:

该类用户的回话作用域:可重复读

# 修改其中一个客户端的回话作用域为读未提交:

sql 复制代码
set session transaction isolation level READ UNCOMMITTED;

2.修改后:

a. 全局的隔离级别:会话作用域的修改独立于全局,全局作用于依旧是可重复读

b. 将被修改隔离级别的客户端:

该用户的回话作用域:读未提交

c. 其他客户端:会话作用域的修改仅影响执行改SQL语句的客户端,其他客户端的会话隔离级别不受影响

该类用户的回话作用域:可重复读

5. 不同隔离级别之间存在的问题

5.1 读未提交 --> 脏读

1. 脏读的含义

在 READ UNCOMMITTED 隔离级别下,事务之间在读取数据时不做任何限制。虽然这样的并发性能非常高,但是会出现大量的数据安全问题。

其中最大的问题就是脏读:假设事务A要修改数据,事务B要读取数据,事务B读取到另一个事务A未提交的修改 ,后续该事务A可能会回滚,回滚后事务B读取的 数据是****无效的

  • 脏读发生始于未完成提交的事务操作,不针对(不同事务之间访问的) 是同一个表还是同一行数据。

2. "脏读"现象的代码演示

-- 示例:客户B读到客户A未提交的数据

所有事务开始前,账户表中存在的数据:

设置客户端B(负责读取数据)的回话隔离级别为读未提交:

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------|
| 会话(客户端)A开启事务A | 会话(户端)B开启事务B |
| # 开启事务A | |
| # 写入一条新数据 # 查看新结果集 | |
| | # 开启事务B |
| | # 查询账户表的数据,有3条记录 |
| # 不提交,选择回滚数据 | |
| | # 再次查询,变成2条记录了 |

事务B前后两次查询到的结果不一致,说明发生了脏读。事务B第一次读到的用户A未提交的**"无效脏数据"**。


补充:"脏读"与"读脏数据"

最需要注意的是,不要把 "读脏数据" 理解成**"某个事务把数据读脏了,使得真实表存储的数据都是无效的"** 。它的意思是**"某个事务对数据进行了一次无效地读取"**。
脏读 (Dirty Read)

  • 是一种现象/问题:事务隔离级别中的一个专业术语

  • 描述的是**"** 一个事务读取到了另一个未提交事务 修改的数据**"**的异常现象

读脏数据 (Reading Dirty Data)

  • 是一种行为/操作:某个事务执行了读取脏数据的行为"事务具体做了什么"

  • 描述的是**"** 某个事务执行了读取脏数据的行为**"**

5.2 读已提交 --> 不可重复读

1. 不可重复读的含义

在读已提交的隔离级别下,事务读取到的都是已提交的数据,彻底解决了脏读的问题。假设回话A开启了事务A,事务A会执行修改操作;此时会话B开启了事务B并进行查询,那么事务B只能查询到事务A开启之前的数据(上一次提交的数据) 。当事务A提交后,事务B才能查询到提交后的数据

不过在读已提交的隔离级别下,仍然存在着隐患,其中最大的隐患是"不可重复读"。

异常现象:同一事务内两次读同一行 ,因为其他事务提交了对该行数据的修改,导致两次读取的数据不一致。

  • 不可重复读发生在两个事务访问同一行数据(一个事务读取,一个事务修改)的情况,针对的是同一行数据

2. "不可重复读"现象的代码演示

-- 示例:事务A要修改"李四"的数据,事务B要读取两次"李四"的数据,且刚好事物B读第2次之前事务A完成并提交了

所有事务开始前,账户表中存在的数据:

设置客户端B回话作用域的隔离级别为读已提交:

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------|
| 会话(客户端)A开启事务A | 会话(客户端)B开启事务B |
| | # 开启事务B |
| # 开启事物A | # 第一次读取:张三900,李四1100 (此时"李四"的数据除事务B外无其他事务访问) |
| # 修改"李四"的余额为2000 | |
| | # 第二次读取:张三900,李四1100 (事务B的隔离级别是读已提交,此时事务A还 没提交,所以查询结果不变) |
| # 提交事物 | |
| | # 第三次读取:张三900,李四2000 |

可以看到,对同一行数据,事务B在事物A提交前后的读取结果并不一致,这就是不可重复读。

5.3 可重复读 --> 幻读

1. MySQL对于"不可重复读"的解决

在MySQL的InnoDB引擎中,"可重复读"级别采用了快照 的方式解决了"不可重复读"的现象。

简单了解:快照、活跃事务列表、事务ID

以订电影院座位类比:

  1. 事务ID = 你的票号(如:A100)
  2. 活跃事务列表 = 当前正在选座的人列表
  3. 一致性读视图 = 你进场时拍的"座位现状快照"
  4. 数据行 = 每个座位状态
  • 你(事务B,票号A100)打开订票页面时,订票APP为你拍了快照。
  • 你看到8排10座是空的(基于快照)。
  • 实际上,小明(事务A)正在预订这个座位**(在活跃列表中)**。
  • 但你不知道,因为你的快照是小明锁定8排10座(事务A修改数据)前拍的
  • 你最后订了6排8座的座位。直到你订完票 (事务B提交),你重新打开订票页面 (事务C的开启),(使用事务C的新快照) 你才知道座位被人选了。

深入了解快照:【非必需掌握的知识点,可以跳过】

快照的全称是一致性读视图(Consistent Read View)。 从使用角度看:它是一个视图;从实现角度看:它是一组可见性判断规则。

  • 快照不能通过show tables看到,因为它不是一个真的视图。
  • 事务在可见性判断规则下可以找到自己可见的版本,每个版本的数据是固定下来的,所以查找到的数据是重复的。
    **事务ID:**每当一个新事务被启动时,InnoDB分配一个递增的全局唯一ID。

**活跃事务列表:**通过存储事务ID的方式,用来记录所有进行中的事务。

  • 注意,活跃的事务也代表着它没有被 commit 。
  • 事务被提交后就会被剔除出活跃事务列表,所以列表中的最小ID对应着当前最老的事务。
    在索引树中,数据页的数据行存储着该行最新的数据 + 指向旧版本数据的指针(详细一点是:键值key + 存储的数据 + 旧版本指针)

该指针指向的区域可以称为版本日志,该日志以链表 的形式连接起在该行中 存在过的旧版本数据 + 创建该版本的事务ID

  1. 当"可重复读"级别的事务启动时会生成一个事务ID,该事务第一次使用查询语句 时,会获取此时活跃事务表中最小的事务ID(最老的事务)

  2. 查询数据时,会先根据key找到数据行,然后读取数据行中的旧版本指针;通过指针找到版本日志,接着根据可见性判断规则来遍历链表:

  • 创建该节点的事务ID > 当前事务的ID ,说明该节点对应的版本不可见,继续遍历下一个节点(上一个版本)。
  • 最小的事务ID <= 创建该节点的事务ID <= 当前事务的ID
    • 创建该节点的事务ID == 当前事务的ID ,说明该版本的创建者和使用者是同一个,可见
    • 若 创建该节点的事务ID 活跃事务列表,说明使用该版本的事务未提交,该版本不可见
    • 若 创建该节点的事务ID不在 活跃事务列表,说明没有事务使用该版本,该版本可见。【用"当前事务的ID"替换掉"创建该节点的事务ID"】
  • 创建该节点的事务ID < 当前事务的ID ,说明该版本没有被任何事务占用,对当前事务可见。【用"当前事务的ID"替换掉"创建该节点的事务ID"】
  1. 修改 某行数据,就会自动创建并保留 该行数据的旧版本快照

  2. 某个版本长时间没有事务访问时就会自动回收。


2. 幻读的含义

"可重复读"级别存在着幻读问题。

异常现象:同一事务内多次执行相同的范围查询语句 时,因其他事务提交了 "插入、删除数据行" 的操作 ,导致前后两次查询的结果集中出现 "新增的幻象行" 或 "消失的行" ,破坏了事务内范围数据的一致性

  • 幻读发生在两个事务访问同一范围内的数据 (一个事务读取,一个事务增删数据行) 的情况,针对的是多行数据的范围查询

补充:虽然"可重复读"级别存在快照机制,但是快照是行级的,它只为每行数据单独记录其历史版本,而不是为整个表(或整个数据库)记录历史版本。所以快照不能解决幻读问题!!!


3. "幻读"现象的代码演示

-- 示例:事务A要对账户表新增"王五"的数据,事务B要对整个账户表读取2次,且刚好事务A新增数据并提交后事务B才读第2次

MySQL的"可重复读"级别使用了 Next-Key 锁,在很大程度上避免了幻读的问题。要想使幻读问题重现,只能用"读已提交"级别进行展示。

所有事务开始前,账户表中存在的数据:

客户端B回话作用域的隔离级别为读已提交:

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|
| 会话(客户端)A开启事务A | 会话(客户端)B开启事务B |
| | # 开启事务B |
| # 开启事务A | # 第一次查询:共2行数据 |
| # 新增一行数据"王五" | |
| # 提交事务A | |
| | # 第2次查询:共3行数据 |

事务B对同一个范围进行查询,两次查询出来的记录数不一致,说明发生了幻读。

5.4 串行化

进⼀步提升事务的隔离级别到 SERIALIZABLE ,此时所有事务串⾏执⾏,可以解决所有并发中的安全问题

总结:不同隔离级别的性能与安全比较

5.5 易混淆点解析

易错1:"脏读"与"读脏数据"

最需要注意的是,不要把 "读脏数据" 理解成**"某个事务把数据读脏了,使得真实表存储的数据都是无效的"** 。它的意思是**"某个事务对数据进行了一次无效地读取"**。
脏读 (Dirty Read)

  • 是一种现象/问题:事务隔离级别中的一个专业术语。

  • 描述的是**"** 一个事务读取到了另一个未提交事务 修改的数据**"** 的异常现象。

读脏数据 (Reading Dirty Data)

  • 是一种行为/操作:某个事务执行了读取脏数据的行为"事务具体做了什么"。

  • 描述的是**"** 某个事务执行了读取脏数据的行为**"。**

易错2:"不可重复读"是不能多次读取数据吗?

"不可重复读"的意思是:对于同一行数据 ,保证不了在多次读取 中,读取到的数据都是一样的。

不是**"不可以重复读取数据"** ,而是**"不一定读到重复的数据"** ,最重要的是读取的对象必须是同一行数据

易错3:幻读为什么被称为"幻读"?

使用同一条范围查询的SQL语句,多次查询出来的结果集却不同。新结果集可能比旧结果集多几条记录,也可能比旧结果集少几条记录。

**明明要查的范围一样的,却出现了忽"大"忽"小"的情况 ---> 就像一个幻影一样,没有一个固定的、清晰的成像。**这就是该异常现象被命名为"幻读"的原因。

易错4:可以说"幻读"是一种特殊的"不可重复读"吗?

从感觉上看,"幻读"像是一种范围上的"不可重复读"。但其实,"幻读"不是"不可重复读"中的一种,它们有着两种本质区别:

产生异常现象的来源不同:

  • 不可重复读:源于另一个事务的修改操作。
  • 幻读:源于另一个事务的新增、删除操作。

造成的一致性破坏不同:

  • 不可重复读:破坏了值的一致性。
  • 幻读:破坏了数据范围的一致性。

如果说"幻读"是一种范围上的"不可重复读",那么它的意思是"幻读"破坏了多个值的一致性【修改一个值是值的一致性被破坏,修改多个值也是值的一致性被破坏】;但在事实上,"幻读"破坏的是数据范围的一致性,两者处于不同维度。

易错5:"脏读"、"不可重复读"和"幻读"分别是在什么时候产生的错误?

脏读、不可重复读和幻读,它们都会导致同一事务内读取到不同的结果。但是三者也有时机上的区别:

【事务A要修改、新增或删除数据,事务B要多次读取数据】

  • a.在事物A开启前
  • b.在事物A进行时
  • c.在事物A提交后

事务B在 b和a时期 或者 b和c时期 读到的两个结果不同,说明发生了脏读

事务B在a和c时期 读到的两个结果不同,而且读取的数据 与 事务A修改的数据 是同一行,说明发生了不可重复读

事务B在a和c时期 读到的两个结果不同,而且读取的多行数据 与 事务A进行插入删除操作所在的 是同一个范围,说明发生了幻读


本期分享完毕,感谢大家的支持Thanks♪(・ω・)ノ

相关推荐
ao_lang2 小时前
数据库范式
数据库·mysql
DBA圈小圈2 小时前
【KingbaseES】V8R6查询长事务语句
数据库·postgresql·database
子超兄2 小时前
MVCC机制简介
数据库·mysql
虹科网络安全2 小时前
艾体宝洞察 | 在 Redis 之上,聊一聊架构思维
数据库·redis·架构
yuguo.im2 小时前
如何查看 Mysql 版本
数据库·mysql
中年程序员一枚2 小时前
让postman调用python的开发接口,链接sqlite数据库,让前后联动起来
数据库·python·postman
weixin_462446233 小时前
解决MongoDB官网下载过慢问题
数据库·mongodb
青蛙大侠公主3 小时前
Spring事务
java·数据库·spring
老华带你飞3 小时前
校务管理|基于springboot 校务管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·spring