首先我们来创建一张表,并写入几条语句。
CREATE TABLE `t` (
`id` int(11) NOT NULL,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c` (`c`)
) ENGINE=InnoDB;
insert into t values(0,0,0),(5,5,5),
(10,10,10),(15,15,15),(20,20,20),(25,25,25);
我们知道的是,在执行更新语句的时候,MySQL 会加行锁。但是仅仅加行锁的话,可能是锁不住的(因为此时记录中可能不存在被锁的行),此时MySQL 里面还有一个 间隙锁
(Gap Lock)的概念。上面的建表语句会形成如下图所示的间隙:
当我们执行如下SQL 语句时,就不止给已经存在的6 条记录加行锁,还要给 7 个间隙加锁(因为 d 字段没有索引,SQL 中被扫描到的行都会加锁,因此是所有记录都会加锁
)。这样就保证了无法再写入 d=5 的记录了,从而保证了SQL 的语义正确。
mysql> select * from t where d = 5 for update;
对于行锁来讲,我们知道读锁和写锁的互斥关系如下。而对于间隙锁来讲,两个间隙锁之间并不互斥,互斥的是间隙锁和往间隙里面插入记录的操作。
比如我们执行如下的SQL,然后会发现后面执行的语句并不会被 Blocked。Session A 加的间隙锁是 (5,10),当Session B再加间隙锁时,也是可以加上的。它们功能的目标就是保护这个间隙不被写入数据,它们之间并不冲突。
间隙锁和行锁被统称为 next-key lock,每个 next-key lock 都是一个前开后闭的区间。如上面的 Demo 就会有 7 个next-key lock:(-∞, 0]、(0, 5]、(5, 10]、(10, 15]、(15, 20]、(20, 25]、(25, +supremum]。间隙锁解决了一些问题,但同时也带来了其他的一些问题。
当 Session A 和 Session B 同时执行相同的事务处理时,可能会造成 B 的 insert 被A 的间隙锁 (5,10) 挡住,A 的 insert 被 B 的间隙锁 (5,10) 挡住,从而造成了死锁。由此看来,间隙锁造成了更大程度上面的锁,从而会影响系统的并发度。
原则1:加锁的基本单位是 next-key lock (前开后闭)。
原则2:查找过程中访问到的对象才会被加锁。
优化1:唯一索引上的等值查询,next-key lock 会退化为行锁。
优化2:普通索引上的等值查询,向右遍历最后一个不满足等值条件时,next-key lock 会退化为间隙锁。
一个 bug:唯一索引上的范围查询,会访问到不满足条件的第一个值为止。
因为 表 t 中没有 id=7 的记录,1、根据原则1 Session A 的加锁范围就是 next-key lock (5, 10];2、根据优化2,id=10 不满足条件,next-key 会退化为间隙锁, Session A 最终的加锁范围就是 (5, 10)。所以 Session B 写入 id=8 的记录会被锁住,而 Session C 对 id=10 的更新会执行成功。
这里感觉是不是很怪,觉得 Session B 也应该被锁住。(注意一下 Session A 里面是对 c = 5加了锁
)
在这个Demo 中还有一个需要注意的是,Session A 里面使用的是 lock in share mode,它只锁覆盖索引。如果使用的是 for update,则满足条件的主键也会被加锁。
看Demo 之前我们先看一下下面 2 条语句的加锁过程,从逻辑上看是相同的,但是加锁过程它们是不同的。
mysql> select * from t where id=10 for update;
mysql> select * from t where id>=10 and id<11 for update;
在最上面给出的基础数据之上,再增加下面的一条数据。
mysql> insert into t values(30,10,30);
写入之后,对于索引 c 来讲有如下结构。可以看出数据之间的间隙又增加了。
对于 delete 语句,其执行加锁的规则和 select … for update 是类似的。
1、Session A 和上一个Demo 不同的是,增加了 limit 2 这个操作,其实扫描到 (c=10,id=30) 这个记录时,就已经停止了。因此最后的加锁范围如下所示:
从上面 limit 的例子我们可以看出,在写SQL的时候,能加限制的就尽量加一下,以减少MySQL 的加锁范围。
前面我们说的 next-key lock 是一个整体的概念,但是实际加锁过程中,next-key lock 还是由 间隙锁 和 行锁分开执行加锁的。
由此可以看出来,next-key lock 是由 2 部分组成的: (5, 10)的间隙锁 和 10 的行锁
。
参考:《极客时间:MySQL实战》、《高性能MySQL》
链接:http://moguhu.com/article/detail?articleId=126