MySQL InnoDB锁类型及幻象读问题
(一)MySQL InnoDB事务模型
(二)MySQL InnoDB锁模型
(三)MySQL InnoDB非锁定一致性读与锁定读
(四)MySQL InnoDB锁类型及幻象读问题
(五)MySQL InnoDB中各类语句加锁方式
(六)事务的提交与回滚极死锁检测、处理和预防
前边根据InnoDB锁对资源的访问限制对锁进行了归类:X、S、IX、IS。从InnoDB锁住的对象来看,InnoDB又可分为Record Lock, Gap Lock 和Next-Key Lock。其中Record Lock只对索引记录加锁,Gap Lock在索引记录之间的”间隙“加锁,这个”间隙“也包含第一条索引记录之前和最后一条索引记录之后的”间隙“。Next-Key Lock是Record Lock和Gap Lock的结合,除了锁住索引记录本身外还锁住索引记录之前的”间隙“。(注意Record Lock只对索引记录加锁,即使创建表时没有指定任何索引(此时InnoDB创建一个隐藏的索引使用该索引加锁))。
Record Lock很好理解,需要特别说明的是Gap Lock 和Next-Key Lock。默认情况下,InnoDB引擎使用REPEATABLE READ 事务隔离级别和innodb_locks_unsafe_for_binlog=0的设置,此时,InnoDB使用Next-Key Lock来避免”幻象“读问题。假定索引包含4,9,12,18几条记录,Record Lock直接在4,9,12,18这几条索引记录上加锁,Gap Lock覆盖的间隙包括(negative infinity,4)、(4,9)、(9,12)、(12,18)、(18,positive infinity),而Next-Key Lock可能锁定的区间包括(negative infinity,4}、(4,9]、(9,12]、(12,18]、(18,positive infinity)。默认隔离级别以及innodb_locks_unsafe_for_binlog=0时若事务持有索引R上的S锁或X锁,另外的事务便无法在R之前的间隙插入记录(这避免幻象读的基本原理)。如果,使用了唯一索引来检索唯一的行(不包括搜索条件只包含多列唯一索引中的部分列的情况)不会使用Gap Lock。例如user表id列上有唯一索引则SELECT * FROM user WHERE id = 100只会在id=100的行上对唯一索引加Record Lock,若该id不存在或者该列上无唯一索引则语句会锁住对应的间隙。
InnoDB中存在一种称为 insert intention gap lock的Gap Lock,由INSERT语句在执行插入操作前设置,用以表明多个事务向相同的索引间隙插入数据时若在间隙内插入的位置不同则不必相互等待。假定有值为4和7的索引记录。两个不同的事务分别同时向4和7之间的间隙插入5和6两条记录,在获取插入行上的X锁前会先获取间隙内的插入意向锁,因为行并不冲突,所以不会相互阻塞。
需要特别注意的是,不同的事务可以在同一gap上持有不兼容的锁类型。也即,对于Gap lock来说,gap X-lock与gap S-lock是等价的。Gap lock仅用来阻止其他事务在gap内插入。
在讲解InnoDB四种事务隔离级别的时候,讲到过”幻象“读问题,也即在”READ COMMITTED“隔离级别下,同一事务的两次相同的查询可能返回不同的结果,或者”REPEATABLE READ“事务隔离级别下,事务B的更新对另一事务A的更新语句可见,且在事务A在对更新后的结果进行了更新后随后同一事务中的SELECT能够看到之前不存在的记录更新后的结果。具体可以参考对应章节的示例。InnoDB使用Next-key Lock来解决”幻读“。InnoDB默认事务隔离级别为REPEATABLE READ,该隔离级别下InnoDB使用Next-key Lock来锁住相关索引记录以及记录之前的”间隙“,以保证其他session中的事务不仅不能更新记录而且不能在其中插入数据,从而避免”幻读“问题。
还用最初的表做实验,删除原有记录并在表t中插入i=4,9,12,20,30几条记录。
先将事务隔离级别调整为READ-COMMITED,我们已经知道,该隔离级别下用非锁定读时可以读取到其他事务中提交的更新,使前后两次同样条件的查询读取到的结果不一致,那么使用锁定读呢?从下边的例子可以看出,在READ-COMMITED隔离级别下,SESSION A中的事务使用锁定读(这里为X锁),可以阻塞SESSION B中的事务对锁定的记录进行更新或删除,”一定程度上“避免了”不可重复读“或者”幻读”,但是,这里的锁只锁定了记录本身,也即Record Lock,没能阻止SESSION B中的事务在锁定的记录之间插入新的记录,因此当其他事物在这些”间隙“内插入记录并提交后SESSION A中的事务能够看到新插入的记录,还是存在”不可重复读“或者”幻读”。
接下来将隔离级别调整为REATABLE READ,该隔离级别下使用非锁定读一般不会读取到其他事物提交的更新,但其他事务中的更新对其更新操作是可见的,当该事务对更新后的记录进行了更新后,同一事物内的查询便可以看到最新的结果,造成前后两次读取结果不一致(前边已有例子进行说明)。那么该隔离级别下使用锁定读呢?会不会也不能避免其他事物在锁定记录的”间隙“插入新的记录呢?答案是否定的,该隔离级别下,InnoDB使用Next-key Lock,除了锁定记录本身外还会锁定记录之间的”间隙”,从而阻止其他事物在读取的记录键插入新的记录,避免前后同一查询条件查询结果却不一致的问题。
需要注意的是,当锁定的索引记录是唯一索引时不会在”间隙“内加锁。可以在应用程序中使用Next-key Lock实现唯一性检查,也即在读取记录确认是否有与带插入记录冲突的记录时使用锁定读,这样在插入的时候便可以放心的插入,因为此时其他事务不能向锁定的范围(记录加间隙)内插入
。