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