当同一查询在不同时间产生不同的行集时,事务中就会出现
所谓的幻影问题。例如,如果 a
SELECT
执行了两次,但第二次返回了第一次未返回的行,则该行是“幻像”行。
假设表的id
列上有一个索引,child
并且您想要读取并锁定表中标识符值大于 100 的所有行,目的是稍后更新所选行中的某些列:
SELECT * FROM child WHERE id > 100 FOR UPDATE;
查询从
id
大于 100 的第一条记录开始扫描索引。让表包含id
值为 90 和 102 的行。如果在扫描范围内的索引记录上设置的锁不会锁定在间隙中进行的插入(在这种情况下,90 和 102 之间的差距),另一个会话可以在表中插入一个新行,其
id
值为 101。如果您要
SELECT
在同一事务中执行相同的操作,您将看到一个id
值为 101 的新行(一个
“幻影”) 在查询返回的结果集中。如果我们把一组行看成一个数据项,那么新的幻影孩子就会违反事务的隔离原则,即事务应该能够运行,使得它读取的数据在事务期间不发生变化。
为防止幻象,InnoDB
使用称为下一个键锁定的算法,该算法结合了索引行锁定和间隙锁定。
InnoDB
执行行级锁定的方式是,当它搜索或扫描表索引时,它会在遇到的索引记录上设置共享或排他锁。因此,行级锁实际上是索引记录锁。此外,索引记录上的 next-key 锁也会影响
索引记录之前的“间隙”。也就是说,下一个键锁是索引记录锁加上索引记录之前的间隙上的间隙锁。如果一个会话记录了共享锁或排他锁R
在一个索引中,另一个会话不能
R
在索引顺序之前的空隙中插入新的索引记录。
在InnoDB
扫描索引时,它还可以锁定索引中最后一条记录之后的间隙。在前面的例子中就是这样:为了防止任何插入到
id
大于 100 的表中,设置的锁
包括在值 102
InnoDB
之后的间隙上的锁
。id
您可以使用下一键锁定在您的应用程序中实施唯一性检查:如果您以共享模式读取数据并且没有看到您要插入的行的重复项,那么您可以安全地插入您的行并且知道在读取期间在您的行的后继者上设置的下一键锁可防止任何人同时为您的行插入重复项。因此,下一键锁定使您能够“锁定”表中不存在的内容。
如第 15.7.1 节,“InnoDB 锁定” 中所述,可以禁用间隙锁定 。这可能会导致幻影问题,因为当间隙锁定被禁用时,其他会话可以将新行插入到间隙中。