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