简单了解 MySQL 中相关的锁 (2)

就像你去图书馆找书,你并不需要每个书架挨着挨着找,直接去服务台用电脑一搜,就知道图书馆有没有这本书。

记录锁

这就是记录锁,是行锁的一种。记录锁的锁定对象是对应那行数据所对应的索引。对索引不太清楚的可以看看这篇文章。

当我们执行SELECT * FROM student WHERE id = 1 FOR UPDATE语句时,就会对值为1的索引加上记录锁。至于要是一张表里没有索引该怎么办?这个问题在上面提到的文章中也解释过了,当一张表没有定义主键时,InnoDB 会创建一个隐藏的RowID,并以此 RowID 来创建聚簇索引。后续的记录锁也会加到这个隐藏的聚簇索引上。

当我们开启一个事务去更新 id = 1 这行数据时,如果我们不马上提交事务,然后再启一个事务去更新 id = 1 的行,此时使用 show engine innodb status查看,我们可以看到lock_mode X locks rec but not gap waiting的字样。

X是排他锁的意思,从这可以看出来,记录锁其实也可以分为共享锁、排他锁模式。当我们使用FOR UPDATE是排他,而使用LOCK IN SHARE MODE 则是共享。

而在上面字样中出现的 gap 就是另一种行锁的实现间隙锁

间隙锁

对于间隙锁(Gap Locks)而言,其锁定的对象也是索引。为了更好的了解间隙锁,我们举个例子。

SELECT name FROM student WHERE age BETWEEN 18 AND 25 FOR UPDATE

假设我们为 age 建立了非聚簇索引,运行该语句会阻止其他事务向 student 表中新增 18-25 的数据,无论表中是否真的有 age 为 18-25 的数据。因为间隙锁的本质是锁住了索引上的一个范围,而 InnoDB 中索引在底层的B+树上的存储是有序的。

再举个例子:

SELECT * FROM student WHERE age = 10 FOR UPDATE;

值得注意的是,这里的 age 不是唯一索引,就是一个简单的非聚簇索引。此时会给 age = 10 的数据加上记录锁,并且锁定 age < 10 的 Gap。如果当前这个事务不提交,其他事务如果要插入一条 age < 10 的数据时,会被阻塞住。

间隙锁是 MySQL 在对性能、并发综合考虑之下的一种折中的解决方案,并且只在**可重复读(RR)下可用,如果当前事务的隔离级别为读已提交(RC)**时,MySQL会将间隙锁禁用。

刚刚说了,记录锁分为共享、排他,间隙锁其实也一样。但是不同于记录锁的一点,共享间隙锁、排他间隙锁相互不互斥,这是怎么回事?

我们还是需要透过现象看到本质,间隙锁的目的是什么?

为了防止其他事务在 Gap 中插入数据

那共享、排他间隙锁在这个目标上是一致的,所以是可以同时存在的。

临键锁

临键锁(Next-Key Locks)是 InnoDB 最后一种行锁的实现,临键锁实际上是记录锁间隙锁的组合。换句话说,临键锁会给对应的索引加上记录锁,并且外加锁定一个区间。

但是并不是所有临键锁都是这么玩的,对于下面的SQL:

SELECT * FROM student WHERE id = 23;

在这种情况下,id是主键,唯一索引,无论其他事务插入了多少数据,id = 23这条数据永远也只有一条。此时再加一个间隙锁就完全没有必要了,反而会降低并发。所以,在使用的索引是唯一索引的时候,临键锁会降级为记录锁

假设我们有10,20,30总共3条索引数据。那么对应临键锁来说,可能锁定的区间就会如下:

(∞, 10]

(10, 20]

(20, 30]

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwpwzx.html