深入理解MySQL的间隙锁
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解MySQL的间隙锁相关的知识,希望对你有一定的参考价值。
参考技术A 因为行锁只能锁住行,但是新插入记录这个动作,要更新的是记录之间的“间隙”。为了解决幻读问题,InnoDB 只好引入新的锁,也就是间隙锁 (Gap Lock)。间隙锁,锁的就是两个值之间的空隙,不允许两个值之间再插一个值。
比如初始化插入了 6 个记录,这就产生了 7 个间隙。分别是 (-∞,0)、(0,5)、(5,10)、(10,15)、(15,20)、(20, 25)、(25, +supremum),间隙锁都是开区间
和行锁不一样的是,跟间隙锁存在冲突关系的,是“往这个间隙中插入一个记录”这个操作。间隙锁之间都不存在冲突关系。
缺点:可能会导致同样的语句锁住更大的范围,影响了并发度。
间隙锁和行锁合称 next-key lock,每个 next-key lock 是前开后闭区间。如果用 select * from t for update 要把整个表所有记录锁起来,就形成了 7 个 next-key lock,分别是 (-∞,0]、(0,5]、(5,10]、(10,15]、(15,20]、(20, 25]、(25, +supremum]。
和间隙锁的最大区别是,next-key lock 为前开后闭区间,这样所有的next-key lock就可以把所有记录锁起来。
加锁规则里面,包含了两个“原则”、两个“优化”和一个“bug”
mysql间隙锁
什么是间隙锁(gap lock)?
间隙锁是一个在索引记录之间的间隙上的锁。
间隙锁的作用?
保证某个间隙内的数据在锁定情况下不会发生任何变化。比如我mysql默认隔离级别下的可重复读(RR)。
当使用唯一索引来搜索唯一行的语句时,不需要间隙锁定。如下面语句的id列有唯一索引,此时只会对id值为10的行使用记录锁。
select * from t where id = 10 for update;// 注意:普通查询是快照读,不需要加锁
如果,上面语句中id列没有建立索引或者是非唯一索引时,则语句会产生间隙锁。
如果,搜索条件里有多个查询条件(即使每个列都有唯一索引),也是会有间隙锁的。
需要注意的是,当id列上没有索引时,SQL会走聚簇索引的全表扫描进行过滤,由于过滤是在MySQL Server层面进行的。因此每条记录(无论是否满足条件)都会被加上X锁。但是,为了效率考量,MySQL做了优化,对于不满足条件的记录,会在判断后放 锁,最终持有的,是满足条件的记录上的锁。但是不满足条件的记录上的加锁/放锁动作是不会省略的。所以在没有索引时,不满足条件的数据行会有加锁又放锁的耗时过程。
间隙的范围?
根据检索条件向下寻找最靠近检索条件的记录值A作为左区间,向上寻找最靠近检索条件的记录值B作为右区间,即锁定的间隙为(A,B] 左开右闭。
通过上面的描述,感觉很抽象?
没关系,下面我们来通过具体的实验来说明gap锁。再次强调(当使用唯一索引来搜索唯一行的语句时,不需要间隙锁定)
我们先创建一张数据表:
MariaDB [locktest]> create table gaplockt(
-> id int not null,
-> name varchar(255) not null primary key,
-> key `id_index` (`id`)
-> );
在向表中插入一些数据:
MariaDB [locktest]> insert into gaplockt values(1,\'panchao\'),(5,\'songzuer\'),(10,
\'yangmi\');
现在我们的表情况是这样的:
MariaDB [locktest]> select * from gaplockt;
+----+----------+
| id | name |
+----+----------+
| 1 | panchao |
| 5 | songzuer |
| 10 | yangmi |
+----+----------+
接下来我们设置autocommit = 0;
MariaDB [locktest]> set autocommit = 0;
MariaDB [locktest]> select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
| 0 |
+--------------+
现在我们的准备工作已经做完了,我们再事物A中给记录添加一个写锁:
MariaDB [locktest]> select * from gaplockt where id = 5 for update;
+----+----------+
| id | name |
+----+----------+
| 5 | songzuer |
+----+----------+
由于id不是唯一索引,表上加上相应的gap锁。如下图:
以上语句会给表加上的gap锁包括(gap2和gap3),也就是1~5U5~10,特别注意这个区间为左开右闭区间(1,10],没理解没关系,我们接下来通过实验来验证。
实验1、
MariaDB [locktest]> insert into gaplockt values(6,\'jingruyang\');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
MariaDB [locktest]> insert into gaplockt values(4,\'jingruyang\');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
MariaDB [locktest]> insert into gaplockt values(1,\'jingruyang\');
Query OK, 1 row affected (0.00 sec)
MariaDB [locktest]> insert into gaplockt values(10,\'jingruyang10\');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
通过以上实验,我们可以看到当id = 1的时候,我们是能添加数据的,但是当id = 10的时候我们是不能添加数据的,事实证明,我们的这个gap锁区间为(1,10]
实验2、
接下来我们来讨论gap1和gap4的问题。
当我们执行以下语句的时候,或收获gap4锁。
MariaDB [locktest]> select * from gaplockt where id = 100 for update;
那么gap4锁的范围是多少呢? 是(10,100]吗?
直接告诉你们答案,不是,它的范围是(10,+∞),下面我们通过实验来验证。
MariaDB [locktest]> insert into gaplockt values(99,\'jingruyang99\');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
MariaDB [locktest]> insert into gaplockt values(100,\'jingruyang100\');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
MariaDB [locktest]> insert into gaplockt values(1000,\'jingruyang1000\');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
可以看到id=99 id= 100 id=1000 都是上了锁的,所以gap4的区间为(10,+∞)
我们再来讨论gap1
执行以下语句获取gap1
MariaDB [locktest]> select * from gaplockt where id = -100 for update;
gap1的区间我直接告诉大家,(-∞,1)
下面我们通过实验证明。
MariaDB [locktest]> insert into gaplockt values(-99,\'jingruyang99\');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
MariaDB [locktest]> insert into gaplockt values(-100,\'jingruyang99\');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
MariaDB [locktest]> insert into gaplockt values(-10000,\'jingruyang99\');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
看到上面的结果,就不用我多说什么了吧。
以下是两个比较有参考价值的链接,有兴趣的朋友可以看一下。
以上是关于深入理解MySQL的间隙锁的主要内容,如果未能解决你的问题,请参考以下文章