innodb锁机制探究(二)---间隙锁(1)

//

innodb锁机制探究(二)---间隙锁

//

Innodb中的锁算法

innodb中常用的锁算法一般有三种,分别是

1、Record lock,行记录锁

2、Gap Lock,间隙锁

3、Next-key Lock,next key锁

其中Next key锁是Gap锁和Record锁的结合,他锁定的是一个范围,并且锁定记录本身。

也就是说,next key锁不是一个单独的锁,就我理解,它其实是一个概念,这个概念是由上面两个锁的概念组合而来的。记录锁很好理解,就是对某个记录的锁定,今天我们主要说说间隙锁。

间隙锁简介

间隙锁定是锁定索引记录之间的间隙,或者锁定第一个和最后一个记录之间的间隙。组织其他事务将值插入到这个间隙中来,间隙可能跨越单个索引值,多个索引值,也有可能为空。

可重复读级别下才会有间隙锁!!!!

可重复读级别下才会有间隙锁!!!!

可重复读级别下才会有间隙锁!!!!

间隙锁定之间不存在冲突关系,这点也很重要。如果一个事务对某个间隙中间加了锁,那么其他事务也可以在这个间隙中加锁,这些操作不冲突。他的存在,仅仅是为了防止其他事务在这个间隙中插入记录。

间隙锁实例

为了实现间隙锁,我们可以通过以下的例子来查看,首先我们创建一个表,包含id和age两个字段,在age上创建一般的索引,创建语句如下,然后我们插入一些记录:

mysql--dba_admin@127.0.0.1:yeyztest ::>>show create table child\G         
*************************** 1. row ***************************
       Table: child
Create Table: CREATE TABLE `child` (
  `id` int() NOT NULL,
  `age` int() NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_age` (`age`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
 row in set (0.00 sec)


mysql--dba_admin@127.0.0.1:yeyztest ::>>truncate table child;
Query OK,  rows affected (0.01 sec)

mysql--dba_admin@127.0.0.1:yeyztest ::>>insert into child values (,); 
Query OK,  row affected (0.00 sec)

mysql--dba_admin@127.0.0.1:yeyztest ::>>insert into child values (,);
Query OK,  row affected (0.00 sec)

mysql--dba_admin@127.0.0.1:yeyztest ::>>insert into child values (,);
Query OK,  row affected (0.00 sec)

mysql--dba_admin@127.0.0.1:yeyztest ::>>insert into child values (,);
Query OK,  row affected (0.00 sec)

创建表child,然后插入记录10,11,13,20这四条记录,然后我们开始测试间隙锁定,如下:

我们可以发现,插入age等于9或者21的记录可以插入,其他的记录都插入不进去,原因是我们的表中原来的记录是10,11,13,20,那么这个表的间隙就被分成了:

(negative infinity,10]

(10,11],

(11,13],

(13,20],

(20,positive infinity)

需要注意的是,negative infonity和positive infinity是最大记录和最小记录,如果对这两个记录不明确的话,不要紧,可以查看我们4月9号的文章,里面有讲到innodb数据页结构里面的最小记录和最大记录。

因为我们在session 1上锁定了id=13的记录,所以在session B上就不能访问11-13以及13=20之间的记录了,也就是说间隙锁就是(11,20),而本身10和11这两条记录都存在,所以在间隙前面可以插入的最大记录就是9,间隙后面可以插入的最大记录是21.

现在我们知道,在整个插入的过程中发生了锁,我们是用show engine innodb status来查看锁信息,如下:

------------
TRANSACTIONS
------------
Trx id counter 
Purge done for trx's n:o < 1329883 undo n:o < 0 state: running but idle
History list length 2305
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 1329878, ACTIVE 19 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1
MySQL thread id 3938833, OS thread handle 140584442394368, query id 105210346 127.0.0.1 dba_admin update
INSERT INTO child (id,age) VALUES (12,12)
------- TRX HAS BEEN WAITING 19 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 168 page no 4 n bits 72 index idx_age of table `yeyztest`.`child` trx id 1329878 lock_mode X locks gap before rec insert intention waiting
------------------
---TRANSACTION 1329859, ACTIVE 52 sec
4 lock struct(s), heap size 1136, 3 row lock(s)
MySQL thread id 3938791, OS thread handle 140584421222144, query id 105210793 127.0.0.1 dba_admin starting
show engine innodb status

可以看到红色的部分就说明了存在gap锁的信息,而且给出了trx的id值,我们还可以使用之前讲过的information_schema中的innodb_trx和innodb_locks表来查看相关的锁信息,这里不再赘述。

间隙锁导致的死锁问题

因为间隙锁之间不会产生影响,可以同时存在,所以就有了产生死锁的可能,我们看下面这个例子,首先,经过上面的操作,我们现在表里面的数据变成了:

mysql:yeyztest ::>>select * from child;
+----+-----+
| id | age |
+----+-----+
|   |    |
|  |   |
|  |   |
|  |   |
|  |   |
|  |   |
+----+-----+
 rows in set (0.00 sec)

然后我们进行如下的操作:

可以看到,当我们锁定age=15的值的时候,由于这个值不存在,所以锁定了区间(13,20),而在session B上也锁定了这个区间,由于间隙锁不存在冲突,所以session B这个语句执行成功。也就是说,两个会话都锁定了这个区间,此时我们在session A上插入age=15的记录,可以发现锁住了,迟迟没有操作结果,而在session B上插入age=15的记录,则直接提示deadlock found,此时session A上的语句执行成功,也就是说这两个会话上的insert操作互相相成了死锁,innodb此时进行了优化,所以才可以输出最终的结果。

简单总结一下

1、间隙锁锁定的是某个索引记录之前和之后的一个间隙范围。

2、间隙锁之间互不影响,可以在锁定的区间再次添加间隙锁。

3、间隙锁可能造成死锁

4、间隙锁是在RR隔离级别下特有的

5、间隙锁只影响一般索引,对于唯一索引和主键,情况有些许不同,下篇文章我们会着重分析。

本文分享自微信公众号 - DBA随笔(gh_acc2bbc0d447)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-05-28

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券