『MySQL』 事务 - RR隔离级别下幻读现象的扩展理解

关于幻读的理解
前言
本篇文章是解释自己对幻读的理解以及某些操作的理解; 起因是多次所见识的幻读理解都有所不同, 最初所发现的操作现象为如下:

1 现象
在事务一相关的笔记中我们提到, 在`RR`隔离级别下仍可能出现幻读现象, 彼时我们的示例操作是这样的:- 两个事务(事务
A与事务B)同时开启, 并使用SELECT快照读创建出快照; - 事务
A通过INSERT插入一条数据并COMMIT; - 事务
B进行SELECT并未查看到事务A插入的数据; - 事务
B通过范围UPDATE进行数据更新(范围包括事务A所插入的数据); - 此时事务
B的UPDATE操作将事务A所插入的数据读了出来; - 事务
B再次通过SELECT进行查询操作, 查询出了事务A所插入的新数据;
2 幻读的标准

-
P3 (Phantom Read)
"Transaction T1 reads a set of data items satisfying some `
`. Transaction T2 then creates data items that satisfy T1’s ` “事务 T1 读取满足某个 < 搜索条件> 的一组数据项。事务 T2 随后创建了满足 T1 < 搜索条件 > 的数据项并提交。若此时 T1 使用相同的 < 搜索条件 > 重复读取操作,得到的数据项集合将与第一次读取不同。” 文档源自 =="A Critique of ANSI SOL Isolation Levels"== ;` and commits. If T1 then repeats its read with the same ` `, it gets a set of data items different from the first read."
-
Phantom
The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a [`SELECT`](https://dev.mysql.com/doc/refman/8.0/en/select.html) is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row. 所谓的幻影问题发生在事务中,同一个查询在不同时间产生不同的结果集。例如,如果一个 `SELECT` 被执行两次,但第二次返回的行在第一次没有返回,那么这个行就是一个“幻影”行。
3 锁定读
在`MySQL`的官方文档中[MySQL :: MySQL 8.0 Reference Manual :: 17.7.4 幻影行 --- MySQL :: MySQL 8.0 Reference Manual :: 17.7.4 Phantom Rows](https://dev.mysql.com/doc/refman/8.0/en/innodb-next-key-locking.html)提到:To prevent phantoms, `InnoDB` uses an algorithm called next-key locking that combines index-row locking with gap locking. `InnoDB` performs row-level locking in such a way that when it searches or scans a table index, it sets shared or exclusive locks on the index records it encounters. Thus, the row-level locks are actually index-record locks. In addition, a next-key lock on an index record also affects the “gap” before the index record. That is, a next-key lock is an index-record lock plus a gap lock on the gap preceding the index record. If one session has a shared or exclusive lock on record `R` in an index, another session cannot insert a new index record in the gap immediately before `R` in the index order. 为了防止出现幻行, `InnoDB` 使用了一种称为 `next-key locking` 的算法,该算法结合了索引行锁和间隙锁。 `InnoDB` 以一种方式执行行级锁定,即在搜索或扫描表索引时,它会对其遇到的索引记录设置共享锁或排他锁。因此,行级锁实际上是索引记录锁。此外,对索引记录的 next-key 锁也会影响索引记录之前的“间隙”。也就是说,`next-key` 锁是索引记录锁加上对索引记录前间隙的间隙锁。如果一个会话在索引中对记录 `R` 拥有共享锁或排他锁,另一个会话就不能在索引顺序中 `R` 之前的间隙中插入新的索引记录。起初我并未理解何为"间隙锁"(锁定读), 因此我将最初的现象定义为不是幻读, 因为在`RR`隔离级别中已经采用了间隙锁与记录锁进行锁定读, 因此不会发生幻读现象, 可能是数据库为了保证数据一致性所提供的一种策略(这里说明的是数据库数据的一致性而不是事务的一致性);
- 那么何为间隙锁
-
FOR UPDATE
`RR`隔离级别下, 两个事务同时开启并进行快照读; 当事务`A`通过`FOR UPDATE`时为手动开启间隙锁, 间隙锁所锁定的间隙(区间)不允许其他事务进行插入或修改; 因此事务`B`在进行数据插入且数据落入间隙时将会被阻塞;

-
UPDATE
当事务`A`通过`UPDATE`操作时获取间隙锁并设置间隙, 事务`B`在进行数据修改时落入间隙, 因此操作被阻塞;

4 个人结论
关于上述问题, 我对该现象的理解为, 其可以理解为一种幻读现象, 但并不是严格意义的幻读现象, 这种幻读现象是平衡事务与数据库之间所做的处理; 首先幻读的定义是在两次快照读中读到幻影从而导致两次快照读所读出的数据集不同; 而在上述的操作中, 幻影是通过当前读`UPDATE`所产生的, 而不是快照读的操作, 因为在一个事务在进行插入并`COMMIT`后, `MVCC`多版本控制中快照会使得其不能看到其他事务所提交的数据, 读的依旧是快照, 因此不能严格构成幻读定义;- 但为什么说可以是幻读?
- 为什么说是权衡数据库与业务之间的一种策略?
在事务的角度上属于幻读现象, 但针对数据库中的数据以及大局观而言实际上是一种平衡性的有效策略;因此当存在这种业务场景时, 我们需要考虑更高的隔离级别 `Serialize` 以避免这种无法保证的"幻读";
标题:『MySQL』 事务 - RR隔离级别下幻读现象的扩展理解
作者:orion
地址:http://orionpeng.top/articles/2025/11/21/1763879531712.html