Innodb select 加锁机制

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sun_ashe/article/details/82255859

简介

本文对RR事务隔离级别下的select语句进行加锁处理分析。

表结构如下:

mysql> show create table lock_test\G
*************************** 1. row ***************************
       Table: lock_test
Create Table: CREATE TABLE `lock_test` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `age` (`age`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

表中数据

mysql> select * from lock_test;
+----+------+
| id | age  |
+----+------+
|  2 |    5 |
|  3 |   10 |
|  4 |   20 |
|  5 |   30 |
|  7 |   30 |
+----+------+
5 rows in set (0.00 sec)

按照heap_no排序的话,如下图
image_1cm79v2371qgs13ne18qt3u1ifp9.png-15kB
但是为了方便对加锁方式的说明,使其按照如下逻辑排列方式
image_1cm7a0ovvtjjvu417md1ct91mlsm.png-13.2kB

把查询加锁方式,对应到如下几种组合来说明。

组合 隔离级别 where条件
组合一 RR =
组合二 RR >=
组合三 RR <=

详解

每一个组合又可以根据不通的条件取值进行细分

组合一 select * from lock_test where age = ?

1.1 select * from lock_test where age = 2 for update

表中数据,并没有age=2的数据,我们看如何加锁

2018-08-31T09:58:43.441420+08:00 6 [Note] InnoDB: 
trx_id: 323102 create a record lock and add it to lock hash table,  //显示行锁,有显示的锁结构,并且加入到hash表中
space_id: 148 //表的space id
page_no: 4    //记录所在的page id
heap_no: 2    //记录在page内部的heap no
n_bits: 72    //number of bits in the lock bitmap;
primary key: 0 //是否是主键索引上的锁
is record lock: 1 //是否是行锁
is waiting: 0    //是不是没有加锁成功,处于等待
is gap: 1        //是不是间隙锁
is record not gap: 0 //是不是只是锁定记录
is insert intention: 0  //是不是插入意向
lock_mode: 3  (0:LOCK_IS, 1:LOCK_IX, 2:LOCK_S, 3:LOCK_X, 4:LOCK_AUTO_INC, 5:LOCK_NONE)

通过日志显示,此查询操作加显示的排他行锁,并且带有gap属性,只锁gap,不锁记录。

同理对比1.1 select * from lock_test where age=2 lock in share mode

2018-08-31T12:51:57.224886+08:00 7 [Note] InnoDB: 
trx_id: 0  create a record lock and add it to lock hash table,  
space_id: 157 
page_no: 4 
heap_no: 2 
n_bits: 72 
primary key: 0 
is record lock: 1 
is waiting: 0 
is gap: 1 
is record not gap: 0 
is insert intention: 0 
lock_mode: 2  (0:LOCK_IS, 1:LOCK_IX, 2:LOCK_S, 3:LOCK_X, 4:LOCK_AUTO_INC, 5:LOCK_NONE)

对比发现,for update和`lock in share共性如下:

  • 都是间隙锁
  • 间隙锁加在相同的行数据上,heap_no=2,这也是此数据叶中的第一条用户数据,age=5之前的间隙

区别如下:

扫描二维码关注公众号,回复: 3131798 查看本文章
  • 后者是不会被分配事务id的
  • 锁类型前者为LOCK_X,后者为LOCK_S.

根据锁兼容性矩阵,这两个sql是可以共存的,因为间隙锁不阻塞间隙锁,可以参照下图
image_1cm7a6n9e5sa1s2clcf7seukd13.png-21.4kB

那么到底影响哪些其他的sql操作呢

首先我们需要确定这两条SQL间隙锁范围,Innodb间隙锁指的是锁定记录之前的空隙,此例子中,记录为age=2,那么间隙锁定的范围应该是(-∞,5),那么记录age=5在二级索引上到底有没有被锁定呢?

答案是否定的,可以尝试对age=5的记录进行加锁测试,比如下面两条

select * from lock_test where age=5 for update;
//select * from lock_test where age=5 lock in share mode;

select * from lock_test where age=5 for update;来说明,其一共有三把锁,分别为

第一,二级索引age=5的next key lock(记录+间隙)

2018-08-31T13:05:49.834989+08:00 7 [Note] InnoDB: trx_id: 323338 create a record lock and add it to lock hash table,  
space_id: 157 
page_no: 4 
heap_no: 2 
n_bits: 72 
primary key: 0 
is record lock: 1 
is waiting: 0 
is gap: 0 
is record not gap: 0 
is insert intention: 0 
lock_mode: 3  (0:LOCK_IS, 1:LOCK_IX, 2:LOCK_S, 3:LOCK_X, 4:LOCK_AUTO_INC, 5:LOCK_NONE)

第二,对主键索引加锁,可以直接忽略。

2018-08-31T13:05:49.835167+08:00 7 [Note] InnoDB: trx_id: 323338 create a record lock and add it to lock hash table,  
space_id: 157 
page_no: 3 
heap_no: 2 
n_bits: 72 
primary key: 1 
is record lock: 1 
is waiting: 0 
is gap: 0 
is record not gap: 1  //主键只是记录锁,不存在gap属性
is insert intention: 0 
lock_mode: 3  (0:LOCK_IS, 1:LOCK_IX, 2:LOCK_S, 3:LOCK_X, 4:LOCK_AUTO_INC, 5:LOCK_NONE)

第三,二级索引age=5的下一条记录age=10的间隙锁,注意,只锁间隙,不锁记录。

2018-08-31T13:05:49.835355+08:00 7 [Note] InnoDB: trx_id: 323338 create a record lock and add it to lock hash table,  
space_id: 157 
page_no: 4 
heap_no: 3 
n_bits: 72 
primary key: 0 
is record lock: 1 
is waiting: 0 
is gap: 1 
is record not gap: 0 
is insert intention: 0 
lock_mode: 3  (0:LOCK_IS, 1:LOCK_IX, 2:LOCK_S, 3:LOCK_X, 4:LOCK_AUTO_INC, 5:LOCK_NONE)

对于第二和第三来讲,和select * from lock_test where age=2 lock in share mode加锁范围都是不冲突的,模糊的是第一把锁,第一把锁为5的记录锁+5之前的间隙,根据锁的兼容性矩阵来讲,间隙锁锁不阻塞间隙锁,所以这两条sql可以无阻塞的执行。
如下图所示
image_1cm7afph11th31dfe1ij81o9a1eh82g.png-87.1kB

那么问题来了,1.1 select * from lock_test where age = 2 for update,到底阻塞谁呢?

答案就是,插入意向锁。来看个例子

insert into lock_test(age) select 4;

如果你正在测试,会发现,这条sql会被select * from lock_test where age = 2 for update阻塞。来看下这条插入sql的加锁情况

2018-08-31T13:22:19.388272+08:00 7 [Note] InnoDB: trx_id: 323339 create a record lock and add it to lock hash table,  
space_id: 157 
page_no: 4 
heap_no: 2 
n_bits: 72 
primary key: 0 
is record lock: 1 
is waiting: 1 
is gap: 1 
is record not gap: 0 
is insert intention: 1 
lock_mode: 3  (0:LOCK_IS, 1:LOCK_IX, 2:LOCK_S, 3:LOCK_X, 4:LOCK_AUTO_INC, 5:LOCK_NONE)

加锁情况为heap_no=2 也就是age=5 行排他锁,只是gap锁,不锁记录,并且带有插入意向属性。根据锁的兼容性矩阵,插入意向锁和间隙锁是互斥的,所以这条插入操作会被阻塞。如下如所示
image_1cm7aiupc18lffc121a1vki1eku2t.png-84.5kB

换个值插一下,比如

insert into lock_test(age) select 5; //会不会阻塞呢?

好像怎么插都不会阻塞,为什么呢?
要解释这个问题,需要再另外的文章中对insert加锁过程进行详细描述了。

1.2 select * from lock_test where age = 5 for update

下面来看select * from lock_test where age = 5 for update这条语句如何加锁,以及如何阻塞别的sql语句。

第一,对于二级索引 head_no=2,age=5,加next key lock

2018-08-31T13:59:12.492326+08:00 6 [Note] InnoDB: trx_id: 323389 create a record lock and add it to lock hash table,  
space_id: 158 
page_no: 4 
heap_no: 2 
n_bits: 72 
primary key: 0 
is record lock: 1 
is waiting: 0 
is gap: 0 
is record not gap: 0 
is insert intention: 0 
lock_mode: 3  (0:LOCK_IS, 1:LOCK_IX, 2:LOCK_S, 3:LOCK_X, 4:LOCK_AUTO_INC, 5:LOCK_NONE)

第二,主键记录排他锁,无gap属性

2018-08-31T13:59:12.492449+08:00 6 [Note] InnoDB: trx_id: 323389 create a record lock and add it to lock hash table,  
space_id: 158 
page_no: 3 
heap_no: 2 
n_bits: 72 
primary key: 1 
is record lock: 1 
is waiting: 0 
is gap: 0 
is record not gap: 1 
is insert intention: 0 
lock_mode: 3  (0:LOCK_IS, 1:LOCK_IX, 2:LOCK_S, 3:LOCK_X, 4:LOCK_AUTO_INC, 5:LOCK_NONE)

第三,二级索引heap_no=3,age=10的间隙锁。

2018-08-31T13:59:12.492557+08:00 6 [Note] InnoDB: trx_id: 323389 create a record lock and add it to lock hash table,  
space_id: 158 
page_no: 4 
heap_no: 3 
n_bits: 72 
primary key: 0 
is record lock: 1 
is waiting: 0 
is gap: 1 
is record not gap: 0 
is insert intention: 0 
lock_mode: 3  (0:LOCK_IS, 1:LOCK_IX, 2:LOCK_S, 3:LOCK_X, 4:LOCK_AUTO_INC, 5:LOCK_NONE)

其阻塞情况,大家自己可以分析下,哪些sql会阻塞,哪些不会阻塞?以及为什么?

猜你喜欢

转载自blog.csdn.net/sun_ashe/article/details/82255859