总体来说,就是MySQL innoDB引擎要在RR隔离级别之下解决幻读的问题,所以引入了间隙锁。
在进行当前读的情况下,对读出的数据的附近的一整个范围(“间隙”)进行加锁,保证满足查询条件的记录不能被插入。
1、幻读与innoDB的隔离级别(为什么会出现间隙锁这个概念)
根据 ISO/ANSI SQL92 所定义的标准,四级隔离级别中,只有在可串行化的级别之下,才可以防止幻读的出现。
隔离级别
所谓幻读,指的是事务A执行过程中,由于事务B并发插入了一条新数据,事务A两次读数据的内容不一样,出现了“虚幻”的新纪录(phantom,幽灵)。
但实际上各厂商的数据库产品并没有严格遵守SQL92标准,比如Oracle只有 RC 和 Serializable 两级隔离级别。
MySQL的innoDB引擎虽然拥有标准的四级隔离级别(其实也是MVCC等手段模拟出来的),不过它有一点显著的不同,就是在 RR 级别下面已经可以防止幻读的发生。
-
在快照读(snapshot read)的情况下,MySQL通过MVCC(多版本并发控制)来避免幻读。
快照读,读取的是记录的可见版本 (有可能是历史版本),不用加锁。主要应用于无需加锁的普通查询(select)操作。
-
在当前读(current read)的情况下,MySQL通过next-key lock来避免幻读。
当前读,读取的是记录的最新版本,并且会对当前记录加锁,防止其他事务发修改这条记录。加行共享锁(SELECT ... LOCK IN SHARE MODE )、加行排他锁(SELECT ... FOR UPDATE / INSERT / UPDATE / DELETE)的操作都会用到当前度。行锁可参看 MySQL行锁。
下面主要说明跟间隙锁相关的当前读。
2、innoDB的间隙锁/Next-Key Lock
2-1、明确前提条件
- innoDB的间隙锁只存在于 RR 隔离级别
所以希望禁用间隙锁,提升系统性能的时候,可以考虑将隔离级别降为 RC。
2-2、间隙锁/Next-Key Lock
间隙锁在innoDB中的唯一作用就是在一定的“间隙”内防止其他事务的插入操作,以此防止幻读的发生:
比如表A 主键索引ID 非主键索引idx(name) 如果此时A事物执行
select * from A where name = 'a' for update;
因为是非唯一索引,所以此时假如有B事物执行:
update A set name = 'a' where name = 'c';
或者
insert into A (3,'a');
即:B事物 增加或者更新一条非唯一索引数据name='a'的记录,当A事物再次执行
select * from A where name = 'a' for update;的时候 会查出2条数据,此时就发生了幻读;
- 防止间隙内有新数据被插入。
- 防止已存在的数据,更新成间隙内的数据。
innoDB支持三种行锁定方式:
-
行锁(Record Lock):锁直接加在索引记录上面(无索引项时演变成表锁)。
-
间隙锁(Gap Lock):锁定索引记录间隙,确保索引记录的间隙不变。间隙锁是针对事务隔离级别为可重复读或以上级别的。
-
Next-Key Lock :行锁和间隙锁组合起来就是 Next-Key Lock。
innoDB默认的隔离级别是可重复读(Repeatable Read),并且会以Next-Key Lock的方式对数据行进行加锁。Next-Key Lock是行锁和间隙锁的组合,当InnoDB扫描索引记录的时候,会首先对索引记录加上行锁(Record Lock),再对索引记录两边的间隙加上间隙锁(Gap Lock)。加上间隙锁之后,其他事务就不能在这个间隙修改或者插入记录。
当查询的索引含有唯一属性(唯一索引,主键索引)时,Innodb存储引擎会对next-key lock进行优化,将其降为record lock,即仅锁住索引本身,而不是范围。
2-3、何时使用行锁,何时产生间隙锁
对上一节的最后一句做个扩展说明。
- 只使用唯一索引查询,并且只锁定一条记录时,innoDB会使用行锁。
- 只使用唯一索引查询,但是检索条件是范围检索,或者是唯一检索然而检索结果不存在(试图锁住不存在的数据)时,会产生 Next-Key Lock。
- 使用普通索引检索时,不管是何种查询,只要加锁,都会产生间隙锁。
- 同时使用唯一索引和普通索引时,由于数据行是优先根据普通索引排序,再根据唯一索引排序,所以也会产生间隙锁。