默认读者已经了解四个隔离等级、mvcc的机制、各种行锁的作用。
我们已知,RC、RR下:
另外:
那么,RR等级下,到底何时会只用到record lock,何时会用到gap lock/next-key lock?如果用到gap lock/next-key lock,又会施加到多大的范围上呢? 对于所有情况来说:
看起来有很多情况,我们先查阅下官方
查阅官方文档innodb-transaction-isolation-levels.html
REPEATABLE READ
This is the default isolation level for InnoDB
. Consistent reads within the same transaction read the snapshot established by the first read. This means that if you issue several plain (nonlocking) SELECT
statements within the same transaction, these SELECT
statements are consistent also with respect to each other. See Section 15.7.2.3, “Consistent Nonlocking Reads”.
For locking reads (SELECT
with FOR UPDATE
or FOR SHARE
), UPDATE
, and DELETE
statements, locking depends on whether the statement uses a unique index with a unique search condition, or a range-type search condition. InnoDB
locks only the index record found, not the gap before it.InnoDB
locks the index range scanned, using gap locks or next-key locks to block insertions by other sessions into the gaps covered by the range. For information about gap locks and next-key locks, see Section 15.7.1, “InnoDB Locking”.第一段不多说了,Consistent reads下会使用快照读,利用mvcc机制。 第二段翻译如下: locking reads(select ... for update或for share), update、delete语句,其上加的锁取决于语句是用唯一索引进行唯一条件查找,还是用范围查找。
似乎满足唯一索引的唯一条件查找就行,但实际上要严格地说,还要加上一个条件, 即索引要命中。它真正强调的,是要唯一查找,而并没有对单列/联合索引作约束。
也就说:
理解上面的规则?我们首先要理解,在当前读操作中,gap lock和next-key lock是为了防止幻读和不可重复读的,我们现在关注防止幻读的需求。
如果我们的查询可能命中连续的多行
则为了防止幻读,就得加上gap lock或next-key lock。比如,假如有属性(id, col),id为主键,col上有非唯一索引,则执行select * from t where col = 7
时,不管有没命中,都可能查到多行结果。如果查到了一行,为了防止幻读,我们也要在锁住该记录的前后空间,以防其它事务在上面插入了col=7的行,从而造成幻读。
插入新记录
我们注意到,如果用gap lock锁住(4,7)和(7,9),确实能防止记录7的前后插入数据,但也使col=5、col=6等数据的插入被阻塞。这是"不可避免的误伤",是gap lock/next-key lock基于索引的性质所决定的,要想锁住记录7之前的间隙,我们只能获取(4,7)的gap lock。所以我们没法在不"误伤"的情况下阻止幻读。
如果我们的查询最多只可能命中一行,且命中了
只可能命中一行
此时只需获取记录7的记录锁,就能防止不可重复读和幻读。
如果最多只可能命中一行记录,却没命中
只可能命中一行,却没命中
为了防止其它事务在空隙插入col=7的数据,造成幻读,我们不得不用gap lock锁住整个(4,9)的空间,这或许会造成"误伤",但也没办法。
总地来说,就是你的查询应当只可能最多命中一条记录,且确实命中了,才会只用记录锁。否则,为了防止幻读,你不得不在记录的前后加上gap lock,阻止其它事务插入新记录,即使会造成"误伤"。
有了上面的基础,现在我们可以讨论何时使用gap lock了。分为以下情况:
加锁机制图解如下:
加锁机制
那么,Innodb会对多大的范围施加gap lock呢?其实,它是在会造成幻读的空隙都加上锁,以保证并发的安全性,即使可能造成误伤。之所以会造成误伤,是因为我们获取空隙锁时,只能把整个空隙锁定,没法只锁定空隙的一部分。
比如有记录4,6,8,属性为col, 有非唯一索引。
执行select * from t where col > 7
则要锁上(6,8],(8,+∞),因为要防止其他事务在空隙插入col>7的数据,比如新插入col=8的数据,将放置在记录8的前面或后面。
所以,即使可能会"误伤",使col=7的数据无法插入,也没有办法,因为gap lock是施加在索引上的,你只能一次性获取(6,8)整个空隙的锁。
执行select * from t where col > 5 and col < 7
则要锁上(4,6]和(6,8)。