15.7.4 幻读

所谓幻读,是指在同一个事务中多次执行相同的查询,查询出的结果集不同。例如,一条SELECT语句两次,第二次执行时返回了一条第一次执行没有返回的记录,这条记录 就是一条 “幻读” 记录。

假设有一张child表,在id字段上建立由索引,现在要锁定所有比比100大的记录,因为随后要对这些记录进行更新: sql select * from child where id > 100 for update; 这条语句从id>100的第一条记录开始进行扫描。现在表中记录的id值分别是90和102。如果没有锁定扫描区间之间的间隙(本例中,是90-100之间的间隙),另外一个会话 可能会插入一条id=101的记录。如果你在同一个事务中再次执行同一个的SELECT语句,你会在结果集中看到一条id=101的记录(幻读)。如果我们将结果集当成一个数据 条目,新的幻读记录就破坏了事务的隔离性原则。依照隔离性原则,一个事务读取到的数据在事务执行过程中不会发生变化。

要阻止幻读,InnoDB使用了被称为next-key锁的算法(包含了索引记录锁和间隙锁)。InnoDB实现了行级锁,在扫描表的索引时,会在扫描到的索引记录上添加共享锁或 排它锁。因此,行级锁实际上是索引记录锁。一条索引记录上的next-key lock 也影响着这套索引记录之前的间隙。索引,一个next-key lock就是一个索引记录锁加上 一个这条索引记录之前的间隙上的间隙锁。如果一个会话在一条记录R上加了一个共享锁或排它锁,其它会话就不能在R之前的这个间隙插入一条新的记录。当InnoDB扫码索引 时,它也可以锁定最后一条索引记录之后的间隙。借用前面的例子:为了阻止插入id>100的记录,InnoDB会在id=102的索引记录之后的间隙上加锁。

你可以利用next-key lock在你的应用中实现唯一性校验:如果你用共享读模式查询,没有查询到你要插入的记录,你就可以安全的插入,因为在下一条记录上添加的next-key lock会阻止其它会话插入相同的记录。因此,next-kye lock使你可以锁定表中不存在的区间。

间隙锁可以被禁止,见章节 15.7.1 “InnoDB 锁”。这可能会导致幻读问题,因为其它会话可能会在间隙中插入新记录。