个人博客地址
|----------------------------------------|
| 个人博客: 花开富贵 |
文章目录
前言
本篇文章是解释自己对幻读的理解以及某些操作的理解;
起因是多次所见识的幻读理解都有所不同, 最初所发现的操作现象为如下:

起初我认为这是一种幻读 , 因为两次的快照读 结果不一, 但是似乎又似乎不是幻读;
1 现象
在事务一相关的笔记中我们提到, 在RR隔离级别下仍可能出现幻读现象, 彼时我们的示例操作是这样的:
- 两个事务(事务
A与事务B)同时开启, 并使用SELECT快照读创建出快照; - 事务
A通过INSERT插入一条数据并COMMIT; - 事务
B进行SELECT并未查看到事务A插入的数据; - 事务
B通过范围UPDATE进行数据更新(范围包括事务A所插入的数据); - 此时事务
B的UPDATE操作将事务A所插入的数据读了出来; - 事务
B再次通过SELECT进行查询操作, 查询出了事务A所插入的新数据;
在这个示例中, 由于两次SELECT操作所查询到的数据集不同, 因此彼时我们称之为幻读 , 那么实际现象是否为幻读呢;
我们先说结论, 他是幻读 , 但不是严格意义上的幻读;
2 幻读的标准

在ANSI标准中, 关于幻读的解释为如下:
-
P3 (Phantom Read)
"Transaction T1 reads a set of data items satisfying some
<search condition>. Transaction T2 then creates data items that satisfy T1's<search condition>and commits. If T1 then repeats its read with the same<search condition>, it gets a set of data items different from the first read.""事务 T1 读取满足某个 < 搜索条件> 的一组数据项。事务 T2 随后创建了满足 T1 < 搜索条件 > 的数据项并提交。若此时 T1 使用相同的 < 搜索条件 > 重复读取操作,得到的数据项集合将与第一次读取不同。"
文档源自 <"A Critique of ANSI SOL Isolation Levels"</ ;
同样的可以在MySQL的官方文档中可以看到, 对幻读 - 幻影行的定义为:
-
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
SELECTis 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提到:
To prevent phantoms,
InnoDBuses an algorithm called next-key locking that combines index-row locking with gap locking.InnoDBperforms 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 recordRin an index, another session cannot insert a new index record in the gap immediately beforeRin the index order.为了防止出现幻行,
InnoDB使用了一种称为next-key locking的算法,该算法结合了索引行锁和间隙锁。InnoDB以一种方式执行行级锁定,即在搜索或扫描表索引时,它会对其遇到的索引记录设置共享锁或排他锁。因此,行级锁实际上是索引记录锁。此外,对索引记录的 next-key 锁也会影响索引记录之前的"间隙"。也就是说,next-key锁是索引记录锁加上对索引记录前间隙的间隙锁。如果一个会话在索引中对记录R拥有共享锁或排他锁,另一个会话就不能在索引顺序中R之前的间隙中插入新的索引记录。
起初我并未理解何为**"间隙锁"(锁定读)** , 因此我将最初的现象定义为不是幻读 , 因为在RR隔离级别中已经采用了间隙锁 与记录锁 进行锁定读 , 因此不会发生幻读现象, 可能是数据库为了保证数据一致性所提供的一种策略(这里说明的是数据库数据的一致性而不是事务的一致性);
- 那么何为间隙锁
在官方文档中明确提到了, 当使用间隙锁 时, 会对所查询的区域设定一个间隙锁 , 当其他事务试图对间隙内产生修改时, 将会阻塞这些修改;
实际上通常当事务在进行UPDATE操作时或是使用SELECT xxx FOR UPDATE时才会进行使用;
我们下面进行两个示例操作:
-
FOR UPDATE
RR隔离级别下, 两个事务同时开启并进行快照读;
当事务A通过FOR UPDATE时为手动开启间隙锁 , 间隙锁所锁定的间隙(区间)不允许其他事务进行插入或修改;
因此事务B在进行数据插入且数据落入间隙时将会被阻塞; -
UPDATE
当事务
A通过UPDATE操作时获取间隙锁 并设置间隙, 事务B在进行数据修改时落入间隙, 因此操作被阻塞;
我们通常将这些UPDATE与FOR UPDATE称之为锁定读;
但MySQL中提到的这个锁定读 并没有在上述现象发生, 在最初的操作中, 我们使用的是一个事务先进行快照读 , 再进行当前读 (锁定读UPDATE), 而这里的间隙锁 并没有产生实际的效应, 因此是Innodb中的RR隔离级别所未能解决的幻读;
4 个人结论
关于上述问题, 我对该现象的理解为, 其可以理解为一种幻读 现象, 但并不是严格意义的幻读 现象, 这种幻读现象是平衡事务与数据库之间所做的处理;
首先幻读 的定义是在两次快照读 中读到幻影从而导致两次快照读所读出的数据集不同;
而在上述的操作中, 幻影是通过当前读 UPDATE所产生的, 而不是快照读 的操作, 因为在一个事务在进行插入并COMMIT后, MVCC多版本控制中快照会使得其不能看到其他事务所提交的数据, 读的依旧是快照, 因此不能严格构成幻读定义;
- 但为什么说可以是幻读?
这里我们可以站在业务的角度上, 针对上层业务或许我们需要进行先读取, 再进行UPDATE更新, 在此基础上, 我们希望业务调用事务所读取的数据需要保持一致性, 否则很难保证我上层业务的稳定性;
- 为什么说是权衡数据库与业务之间的一种策略?
在上面我们提到了, 站在业务的角度上确实是一种幻读现象, 那么首先以我的理解来看, 实际上事务是因为业务需求才引入的, 属于后来的, 而对于数据库而言需要保证数据库内数据的安全与一致才是我作为数据库应该做的, 那么我能尽量为你上层的业务保持稳定性与一致性是我针对你业务所提供的优化, 但是我不能因为需要对你业务进行优化而放弃我数据库中数据的一致性与安全性;
就例如, 若是在进行范围UPDATE操作时, 未将最新版本以及新的数据交付给你进行修改, 那么有在此间隙中的部分数据将无法进行正确更新, 不仅要保证当前事务, 更需要权衡所有事务以及自身数据安全;
因此我对这个问题的结论为:
在事务的角度上属于幻读现象, 但针对数据库中的数据以及大局观而言实际上是一种平衡性的有效策略;
因此当存在这种业务场景时, 我们需要考虑更高的隔离级别 Serialize 以避免这种无法保证的"幻读";