10 个人 9 个答错,另外 1 个只对一半:数据库的锁
========
Gap Lock,翻译成间隙锁,他指的是在索引记录之间的间隙上的锁,或者在第一个索引记录之前或最后一个索引记录之后的间隙上的锁。
那么,这里所谓的 Gap(间隙)又怎么理解呢?
Gap 指的是 InnoDB 的索引数据结构中可以插入新值的位置。
当你用语句 SELECT…FOR UPDATE 锁定一组行时。InnoDB 可以创建锁,应用于索引中的实际值以及他们之间的间隙。例如,如果选择所有大于 10 的值进行更新,间隙锁将阻止另一个事务插入大于 10 的新值。
既然是锁,那么就可能会影响到数据库的并发性,所以,间隙锁只有在 Repeatable Reads 这种隔离级别中才会起作用。
在 Repeatable Reads 这种隔离下,对于锁定的读操作(select … for update 、 lock in share mode)、update 操作、delete 操作时,会进行如下的加锁:
对于具有唯一搜索条件的唯一索引,InnoDB 只锁定找到的索引记录,而不会锁定间隙。
对于其他搜索条件,InnoDB 锁定扫描的索引范围,使用 gap lock
或 next-key lock 来阻塞其他事务插入范围覆盖的间隙。
也就是说,对于 SELECT FOR UPDATE、LOCK IN SHARE MODE、UPDATE 和 DELETE 等语句处理时,除了对唯一索引的唯一搜索外都会获取 gap 锁或 next-key 锁,即锁住其扫描的范围。
Next-Key Lock
=============
Next-Key 锁是索引记录上的记录锁和索引记录之前间隙上的间隙锁的组合。
假设一个索引包含值 10、11、13 和 20。此索引可能的 next-key 锁包括以下区间:
(-∞, 10]
(10, 11]
(11, 13]
(13, 20]
(20, ∞ ]
对于最后一个间隙,∞不是一个真正的索引记录,因此,实际上,这个 next-key 锁只锁定最大索引值之后的间隙。
所以,Next-Key 的锁的范围都是左开右闭的。
Next-Key Lock 和 Gap Lock 一样,只有在 InnoDB 的 RR 隔离级别中才会生效。
Repeatable Reads 能解决幻读
=====================
很多人看过网上的关于数据库事务级别的介绍,会认为 MySQL 中 Repeatable Reads 能解决不可重复读的问题,但是不能解决幻读,只有 Serializable 才能解决。但其实,这种想法是不对的。
因为 MySQL 跟标准 RR 不一样,标准的 Repeatable Reads 确实存在幻读问题,但 InnoDB 中的 Repeatable Reads 是通过 next-key lock 解决了 RR 的幻读问题的。
因为我们知道,因为有了 next-key lock,所以在需要加行锁的时候,会同时在索引的间隙中加锁,这就使得其他事务无法在这些间隙中插入记录,这就解决了幻读的问题。
关于这个问题,引起过广泛的讨论,可以参考:https://github.com/Yhzhtk/note/issues/42 ,这里有很多大神发表过自己的看法。
MySQL 的加锁原则
==========
前面介绍过了 Record Lock、Gap Lock 和 Next-Key Lock,但是并没有说明加锁规则。关于加锁规则,我是看了丁奇大佬的《MySQL 实战 45 讲》中的文章之后理解的,他总结的加锁规则里面,包含了两个“原则”、两个“优化”和一个“bug”:
原则 1:加锁的基本单位是 next-key lock。是一个前开后闭区间。原则 2:查找过程中访问到的对象才会加锁。优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。
假如,数据库表中当前有以下记录:
当我们执行 update t set d=d+1 where id = 7 的时候,由于表 t 中没有 id=7 的记录,所以:
根据原则 1,加锁单位是 next-key lock,session A 加锁范围就是 (5,10];
根据优化 2,这是一个等值查询 (id=7),而 id=10 不满足查询条件,next-key lock 退化成间隙锁,因此最终加锁的范围是 (5,10)。
当我们执行 select * from t where id>=10 and id<11 for update 的时候:
根据原则 1,加锁单位是 next-key lock,会给 (5,10]加上 next-key lock,范围查找就往后继续找,找到 id=15 这一行停下来
评论