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