MySQL数据库InnoDB引擎行级锁锁定范围详解

前言

每一个数据库几乎都会实现本身的锁机制,锁机制是数据库区别于文件系统的主要标志之一,用于管理对共享资源的并发访问。 html

Mysql数据库InnoDB引擎支持行级锁,也就是说咱们能够对表中某些行数据执行锁定操做,锁定操做的影响是:若是一个事物对表中某行执行了锁定操做,而另外一个事务也须要对一样的行执行锁定操做,这样第二个事务的锁定操做有可能被阻塞,一旦被阻塞第二个事务只能等到第一个事务执行完毕(提交或回滚)或超时。mysql

本文主要介绍InnoDB中的行锁相关概念,重点介绍行锁的锁定范围:sql

  • 什么样的SQL语句会加锁?
  • 加什么样的锁?
  • 加锁语句会锁定哪些行

背景知识

上面咱们简单的介绍了InnoDB的行级锁,为了理解后面的验证部分,须要补充一下背景知识。若是对相应知识很是了解,能够直接跳转到验证部份内容。数据库

1. InnoDB锁的类型

InnoDB引擎使用了七种类型的锁,他们分别是:编程

  • 共享排他锁(Shared and Exclusive Locks)
  • 意向锁(Intention Locks)
  • 记录锁(Record Locks)
  • 间隙锁(Gap Locks)
  • Next-Key Locks
  • 插入意图锁(Insert Intention Locks)
  • 自增锁(AUTO-INC Locks)

本文主要涉及Shared and Exclusive Locks,Record Locks,Gap Locks,Next-Key Locks这几种锁,其余类型锁若是你们感兴趣能够本身深刻了解,在此不在详述。并发

1.1 Shared and Exclusive Locks

共享锁(S锁)和排他锁(X锁)的概念在许多编程语言中都出现过。先来描述一下这两种锁在MySQL中的影响结果:编程语言

  • 若是一个事务对某一行数据加了S锁,另外一个事务还能够对相应的行加S锁,可是不能对相应的行加X锁。
  • 若是一个事务对某一行数据加了X锁,另外一个事务既不能对相应的行加S锁也不能加X锁。

用一张经典的矩阵表格继续说明共享锁和排他锁的互斥关系:测试

-- S X
S 0 1
X 1 1

图中S表示共享锁X表示独占锁,0表示锁兼容1表示锁冲突,兼容不被阻塞,冲突被阻塞。由表可知一旦一个事务加了排他锁,其余个事务加任何锁都须要等待。多个共享锁不会相互阻塞。spa

1.2 Record Locks、Gap Locks、Next-Key Locks

这三种类型的锁都描述了锁定的范围,故放在一块儿说明。code

如下定义摘自MySQL官方文档

  • 记录锁(Record Locks):记录锁锁定索引中一条记录。
  • 间隙锁(Gap Locks):间隙锁要么锁住索引记录中间的值,要么锁住第一个索引记录前面的值或者最后一个索引记录后面的值。
  • Next-Key Locks:Next-Key锁是索引记录上的记录锁和在索引记录以前的间隙锁的组合。

定义中都提到了索引记录(index record)。为何?行锁和索引有什么关系呢?其实,InnoDB是经过搜索或者扫描表中索引来完成加锁操做,InnoDB会为他遇到的每个索引数据加上共享锁或排他锁。因此咱们能够称行级锁(row-level locks)为索引记录锁(index-record locks),由于行级锁是添加到行对应的索引上的。

三种类型锁的锁定范围不一样,且逐渐扩大。咱们来举一个例子来简要说明各类锁的锁定范围,假设表t中索引列有三、五、八、9四个数字值,根据官方文档的肯定三种锁的锁定范围以下:

  • 记录锁的锁定范围是单独的索引记录,就是三、五、八、9这四行数据。
  • 间隙锁的锁定为行中间隙,用集合表示为(-∞,3)、(3,5)、(5,8)、(8,9)、(9,+∞)。
  • Next-Key锁是有索引记录锁加上索引记录锁以前的间隙锁组合而成,用集合的方式表示为(-∞,3]、(3,5]、(5,8]、(8,9]、(9,+∞)。

最后对于间隙锁还须要补充三点:

  1. 间隙锁阻止其余事务对间隙数据的并发插入,这样可有有效的解决幻读问题(Phantom Problem)。正由于如此,并非全部事务隔离级别都使用间隙锁,MySQL InnoDB引擎只有在Repeatable Read(默认)隔离级别才使用间隙锁。
  2. 间隙锁的做用只是用来阻止其余事务在间隙中插入数据,他不会阻止其余事务拥有一样的的间隙锁。这就意味着,除了insert语句,容许其余SQL语句能够对一样的行加间隙锁而不会被阻塞
  3. 对于惟一索引的加锁行为,间隙锁就会失效,此时只有记录锁起做用

2. 加锁语句

前面咱们已经介绍了InnoDB的是在SQL语句的执行过程当中经过扫描索引记录的方式来实现加锁行为的。那哪些些语句会加锁?加什么样的锁?接下来咱们逐一描述:

  • select ... from语句:InnoDB引擎采用多版本并发控制(MVCC)的方式实现了非阻塞读,因此对于普通的select读语句,InnoDB并不会加锁【注1】。
  • select ... from lock in share mode语句:这条语句和普通select语句的区别就是后面加了lock in share mode,经过字面意思咱们能够猜到这是一条加锁的读语句,而且锁类型为共享锁(读锁)。InnoDB会对搜索的全部索引记录加next-key锁,可是若是扫描的惟一索引的惟一行,next-key降级为索引记录锁。
  • select ... from for update语句:和上面的语句同样,这条语句加的是排他锁(写锁)。InnoDB会对搜索的全部索引记录加next-key锁,可是若是扫描惟一索引的惟一行,next-key降级为索引记录锁。
  • update ... where ...语句:。InnoDB会对搜索的全部索引记录加next-key锁,可是若是扫描惟一索引的惟一行,next-key降级为索引记录锁。【注2】
  • delete ... where ...语句:。InnoDB会对搜索的全部索引记录加next-key锁,可是若是扫描惟一索引的惟一行,next-key降级为索引记录锁。
  • insert语句:InnoDB只会在将要插入的那一行上设置一个排他的索引记录锁。

最后补充两点:

  1. 若是一个查询使用了辅助索引而且在索引记录加上了排他锁,InnoDB会在相对应的聚合索引记录上加锁。
  2. 若是你的SQL语句没法使用索引,这样MySQL必须扫描整个表以处理该语句,致使的结果就是表的每一行都会被锁定,而且阻止其余用户对该表的全部插入。

SQL语句验证

闲言少叙,接下来咱们进入本文重点SQL语句验证部分。

1.测试环境

数据库:MySQL 5.6.35
事务隔离级别:Repeatable read
数据库访问终端:mysql client

2.验证场景

2.1 场景一

建表:

CREATE TABLE `user` (
 `id` int(11) NOT NULL,
 `name` varchar(8) NOT NULL,
 PRIMARY KEY (`id`),
 KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

插入数据:

INSERT INTO `user` (`id`, `name`) VALUES ('1', 'a');
INSERT INTO `user` (`id`, `name`) VALUES ('3', 'c');
INSERT INTO `user` (`id`, `name`) VALUES ('5', 'e');
INSERT INTO `user` (`id`, `name`) VALUES ('7', 'g');
INSERT INTO `user` (`id`, `name`) VALUES ('9', 'i');

首先咱们执行SQL语句的模板:

步骤 client 1 client 2
1 begin; --
2 SELECT * FROM user where name='e' for update; --
3 -- begin;
4 -- INSERT INTO `user` (`id`, `name`) VALUES (10, #{name});
5 rollback; --
6 -- rollback;

替换步骤5中name的值,观察结果:

name的值 执行结果
a 不阻塞
b 不阻塞
d 阻塞
e 阻塞
f 阻塞
h 不阻塞
i 不阻塞

观察结果,咱们发现SQL语句
SELECT * FROM user where name='e' for update
一共锁住索引name中三行记录,(c,e]区间应该是next-key锁而(e,h)区间是索引记录e后面的间隙。

接下来咱们肯定next-key锁中哪部分是索引记录锁哪部分是间隙锁。

执行SQL语句的模板:

步骤 client 1 client 2
1 begin; --
2 SELECT * FROM user where name='e' for update; --
3 -- SELECT * FROM user where name=#{name} for update;
5 rollback; --
6 -- rollback;

替换步骤5中name的值,观察结果:

name的值 执行结果
d 不阻塞
e 阻塞
f 不阻塞

由于间隙锁只会阻止insert语句,因此一样的索引数据,insert语句阻塞而select for update语句不阻塞的就是间隙锁,若是两条语句都阻塞就是索引记录锁。

观察执行结果可知,d和f为间隙锁,e为索引记录锁。

结论:经过两条SQL,咱们肯定了对于辅助索引name在查询条件为 where name='e' 时的加锁范围为(c,e],(e,g),其中:

  • 对SQL语句扫描的索引记录e加索引记录锁[e]。
  • 锁定了e前面的间隙,c到e之间的数据(c,e)加了间隙锁
  • 前两个构成了next-key锁(c,e]。
  • 值得注意的是还锁定了e后面的间隙(e,g)。

说的这里细心的读者可能已经发现咱们的测试数据中没有间隙的边界数据c和g。接下来咱们就对间隙边界值进行测试。

执行SQL语句的模板:

步骤 client 1 client 2
1 begin; --
2 SELECT * FROM user where name='e' for update; --
3 -- begin;
4 -- INSERT INTO `user` (`id`, `name`) VALUES (#{id}, #{name});
5 rollback; --
6 -- rollback;

替换步骤5中id,name的值,观察结果:

id的值 name=c 执行结果 id的值 name=g 执行结果
-- -- -- -3 g 组塞
-- -- -- -2 g 阻塞
-1 c 不阻塞 -1 g 阻塞
1 c 不阻塞 1 g 不阻塞
2 c 不阻塞 2 g 阻塞
3 c 不阻塞 3 g 不阻塞
4 c 阻塞 4 g 阻塞
5 c 阻塞 5 g 阻塞
6 c 阻塞 6 g 阻塞
7 c 不阻塞 7 g 不阻塞
8 c 阻塞 8 g 不阻塞
9 c 不阻塞 9 g 不阻塞
10 c 阻塞 10 g 不阻塞
11 c 阻塞 - - -
12 c 阻塞 - - -

经过观察以上执行结果,咱们发现,name等于c和e时insert语句的结果随着id值得不一样一下子锁定,一下子不锁定。那必定是id列加了锁才会形成这样的结果。

若是先不看id=5这一行数据的结果,咱们发现一个规律:

  • name=c时,name=c对应的id=3的id聚合索引数据记录以后的间隙(3,5),(5,7),(7,9),(9,∞)都被加上了锁。
  • name=e时,name=e对应的id=7的id聚合索引数据记录以前的间隙(5,7),(3,5),(1,3),(-∞,1)都被加上了锁。
  • 咱们可用select * from user where id = x for update;语句判断出以上间隙上加的锁都为间隙锁。

接下来咱们解释一下id=5的锁定状况

执行SQL语句的模板:

步骤 client 1 client 2
1 begin; --
2 SELECT * FROM user where name='e' for update; --
3 -- SELECT * FROM user where id=#{id} for update;
5 rollback; --
6 -- rollback;

替换步骤5中id的值,观察结果:

id的值 执行结果
3 不阻塞
4 不阻塞
5 阻塞
6 不阻塞
7 不阻塞

经过观察执行结果可知,id=5的聚合索引记录上添加了索引记录锁。根据MySQL官方文档描述,InnoDB引擎在对辅助索引加锁的时候,也会对辅助索引所在行所对应的聚合索引(主键)加锁。而主键是惟一索引,在对惟一索引加锁时,间隙锁失效,只使用索引记录锁。因此SELECT * FROM user where name='e' for update;不只对辅助索引name=e列加上了next-key锁,还对对应的聚合索引id=5列加上了索引记录锁。

最终结论:
对于SELECT * FROM user where name='e' for update;一共有三种锁定行为:

  1. 对SQL语句扫描过的辅助索引记录行加上next-key锁(注意也锁住记录行以后的间隙)。
  2. 对辅助索引对应的聚合索引加上索引记录锁。
  3. 当辅助索引为间隙锁“最小”和“最大”值时,对聚合索引相应的行加间隙锁。“最小”锁定对应聚合索引以后的行间隙。“最大”值锁定对应聚合索引以前的行间隙。

上面咱们将对辅助索引加锁的状况介绍完了,接下来咱们测试一下对聚合索引和惟一索引加锁。

2.2 场景二

建表:

CREATE TABLE `user` (
  `id` int(11) NOT NULL,
  `name` varchar(8) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `index_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

注意与场景一表user不一样的是name列为惟一索引。

插入数据:

INSERT INTO `user` (`id`, `name`) VALUES ('1', 'a');
INSERT INTO `user` (`id`, `name`) VALUES ('3', 'c');
INSERT INTO `user` (`id`, `name`) VALUES ('5', 'e');
INSERT INTO `user` (`id`, `name`) VALUES ('7', 'g');
INSERT INTO `user` (`id`, `name`) VALUES ('9', 'i');

首先咱们执行SQL语句的模板:

步骤 client 1 client 2
1 begin; --
2 SELECT * FROM user where name='e' for update;
3 -- begin;
4 -- INSERT INTO `user` (`id`, `name`) VALUES (10, #{name});
5 rollback; --
6 -- rollback;

替换步骤5中name的值,观察结果:

name的值 执行结果
a 不阻塞
b 不阻塞
c 不阻塞
d 不阻塞
e 阻塞
f 不阻塞
g 不阻塞
h 不阻塞
i 不阻塞

由测试结果可知,只有name='e'这行数据被锁定。

经过SQL语句咱们验证了,对于惟一索引列加锁,间隙锁失效,

2.3 场景三

场景一和场景二都是在查询条件等于的状况下作出的范围判断,如今咱们尝试一下其余查询条件,看看结论是否一致。

借用场景一的表和数据。

建表:

CREATE TABLE `user` (
  `id` int(11) NOT NULL,
  `name` varchar(8) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `index_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

插入数据:

INSERT INTO `user` (`id`, `name`) VALUES ('1', 'a');
INSERT INTO `user` (`id`, `name`) VALUES ('3', 'c');
INSERT INTO `user` (`id`, `name`) VALUES ('5', 'e');
INSERT INTO `user` (`id`, `name`) VALUES ('7', 'g');
INSERT INTO `user` (`id`, `name`) VALUES ('9', 'i');

执行SQL语句的模板:

步骤 client 1 client 2
1 begin; --
2 SELECT * FROM user where name>'e' for update; --
3 -- begin;
4 -- INSERT INTO `user` (`id`, `name`) VALUES ('10', #{name});
5 rollback; --
6 -- rollback;

替换步骤5中name的值,观察结果:

name的值 执行结果
a 阻塞
b 阻塞
c 阻塞
d 阻塞
e 阻塞
f 阻塞
g 阻塞
h 阻塞
i 阻塞

这个结果是否是和你想象的不太同样,这个结果代表where name>'e'这个查询条件并非锁住'e'列以后的数据,而锁住了全部name列中全部数据和间隙。这是为何呢?

咱们执行如下的SQL语句执行计划:

explain select * from user where name>'e' for update;

执行结果:

+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+
| id | select_type | table | type  | possible_keys | key        | key_len | ref  | rows | Extra                    |
+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+
|  1 | SIMPLE      | user  | index | index_name    | index_name | 26      | NULL |    5 | Using where; Using index |
+----+-------------+-------+-------+---------------+------------+---------+------+------+--------------------------+
1 row in set (0.00 sec)

若是你的结果与上面不一样先执行一下OPTIMIZE TABLE user;再执行以上语句。

经过观察SQL语句的执行计划咱们发现,语句使用了name列索引,且rows参数等于5,user表中一共也只有5行数据。SQL语句的执行过程当中一共扫描了name索引记录5行数据且对这5行数据都加上了next-key锁,符合咱们上面的执行结果。

接下来咱们再制造一组数据。
建表:

CREATE TABLE `user` (
 `id` int(11) NOT NULL,
 `name` varchar(8) NOT NULL,
 `age` int(11) NOT NULL,
 PRIMARY KEY (`id`),
 KEY `index_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

插入数据:

INSERT INTO `user` (`id`, `name`,`age`) VALUES ('1', 'a','15');
INSERT INTO `user` (`id`, `name`,`age`) VALUES ('3', 'c','20');
INSERT INTO `user` (`id`, `name`,`age`) VALUES ('5', 'e','16');
INSERT INTO `user` (`id`, `name`,`age`) VALUES ('7', 'g','19');
INSERT INTO `user` (`id`, `name`,`age`) VALUES ('9', 'i','34');

这张表和前表的区别是多了一列非索引列age

咱们再执行一下一样的SQL语句执行计划:

explain select * from user where name>'e' for update;

执行结果:

+----+-------------+-------+-------+---------------+------------+---------+------+------+-----------------------+
| id | select_type | table | type  | possible_keys | key        | key_len | ref  | rows | Extra                 |
+----+-------------+-------+-------+---------------+------------+---------+------+------+-----------------------+
|  1 | SIMPLE      | user  | range | index_name    | index_name | 26      | NULL |    2 | Using index condition |
+----+-------------+-------+-------+---------------+------------+---------+------+------+-----------------------+
1 row in set (0.00 sec)

是否是和第一次执行结果不一样了,rows参数等于2,说明扫描了两行记录,结合SQL语句select * from user where name>'e' for update;执行后返回结果咱们判断这两行记录应该为g和i。

由于select * from user where name>'e' for update;语句扫描了两行索引记录分别是g和i,因此咱们将g和i的锁定范围叠就能够获得where name>'e'的锁定范围:

  1. 索引记录g在name列锁定范围为(e,g],(g,i)。索引记录i的在name列锁定范围为(g,i],(i,+∞)。二者叠加后锁定范围为(e,g],(g,i],(i,+∞)。其中g,i为索引记录锁。
  2. g和i对应id列中的7和9加索引记录锁。
  3. name列的值为锁定范围上边界e时,还会在e所对应的id列值为5以后的全部值之间加上间隙锁,范围为(5,7),(7,9),(9,+∞)。下边界为+∞无需考虑。

接下来咱们逐一测试:

首先测试验证了next-key锁范围,执行SQL语句的模板:

步骤 client 1 client 2
1 begin; --
2 SELECT * FROM user where name>'e' for update; --
3 -- begin;
4 -- INSERT INTO `user` (`id`, `name`, `age`) VALUES ('10', #{name},'18');
5 rollback; --
6 -- rollback;

替换步骤5中name的值,观察结果:

name的值 执行结果
a 不阻塞
b 不阻塞
c 不阻塞
d 不阻塞
f 阻塞
g 阻塞
h 阻塞
i 阻塞
j 阻塞
k 阻塞

下面验证next-key锁中哪部分是间隙锁,哪部分是索引记录锁,执行SQL语句的模板:

步骤 client 1 client 2
1 begin; --
2 SELECT * FROM user where name>'e' for update; --
3 -- SELECT * FROM user where name=#{name} for update;
5 rollback; --
6 -- rollback;

替换步骤5中name的值,观察结果:

name的值 执行结果
e 不阻塞
f 不阻塞
g 阻塞
h 不阻塞
i 阻塞
j 不阻塞

接下来验证对id列加索引记录锁,执行SQL语句的模板:

步骤 client 1 client 2
1 begin; --
2 SELECT * FROM user where name>'e' for update; --
3 -- SELECT * FROM user where id=#{id} for update;
5 rollback; --
6 -- rollback;

替换步骤5中id的值,观察结果:

id的值 执行结果
5 不阻塞
6 不阻塞
7 阻塞
8 不阻塞
9 阻塞
10 不阻塞

最后咱们验证name列的值为边界数据e时,id列间隙锁的范围,执行SQL语句的模板:

步骤 client 1 client 2
1 begin; --
2 SELECT * FROM user where name>'e' for update; --
3 -- begin;
4 -- INSERT INTO `user` (`id`, `name`,`age`) VALUES (#{id}, 'e','18');
5 rollback; --
6 -- rollback;

替换步骤5中id的值,观察结果:

id的值 执行结果
-1 不阻塞
1 不阻塞
2 不阻塞
3 不阻塞
4 不阻塞
5 不阻塞
6 阻塞
7 阻塞
8 阻塞
9 阻塞
10 阻塞
11 阻塞
12 阻塞

注意7和9是索引记录锁记录锁

观察上面的全部SQL语句执行结果,能够验证select * from user where name>'e' for update的锁定范围为此语句扫描name列索引记录g和i的锁定范围的叠加组合。

2.4 场景四

咱们经过场景三验证了普通索引的范围查询语句加锁范围,如今咱们来验证一下惟一索引的范围查询状况下的加锁范围。有了场景三的铺垫咱们直接跳过扫描所有索引的状况,建立能够扫描范围记录的表结构并插入相应数据测试。

建表:

CREATE TABLE `user` (
  `id` int(11) NOT NULL,
  `name` varchar(8) NOT NULL,
  `age` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `index_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

插入数据:

INSERT INTO `user` (`id`, `name`,`age`) VALUES ('1', 'a','15');
INSERT INTO `user` (`id`, `name`,`age`) VALUES ('3', 'c','20');
INSERT INTO `user` (`id`, `name`,`age`) VALUES ('5', 'e','16');
INSERT INTO `user` (`id`, `name`,`age`) VALUES ('7', 'g','19');
INSERT INTO `user` (`id`, `name`,`age`) VALUES ('9', 'i','34');

和场景三表惟一不一样是name列为惟一索引。

SQL语句select * from user where name>'e'扫描name列两条索引记录g和i。若是须要只对g和i这两条记录加上记录锁没法避免幻读的发生,索引锁定范围应该仍是两条数据next-key锁锁的组合:(e,g],(g,i],(i,+∞)。其中g,i为索引记录锁

咱们经过SQL验证咱们的结论,执行SQL语句的模板:

步骤 client 1 client 2
1 begin; --
2 SELECT * FROM user where name>'e' for update; --
3 -- begin;
4 -- INSERT INTO `user` (`id`, `name`, `age`) VALUES ('10', #{name},'18');
5 rollback; --
6 -- rollback;

替换步骤5中name的值,观察结果:

name的值 执行结果
a 不阻塞
b 不阻塞
c 不阻塞
d 不阻塞
f 阻塞
g 阻塞
h 阻塞
i 阻塞
j 阻塞
k 阻塞

下面验证next-key锁中哪部分是间隙锁,哪部分是索引记录锁,执行SQL语句的模板:

步骤 client 1 client 2
1 begin; --
2 SELECT * FROM user where name>'e' for update; --
3 -- SELECT * FROM user where name=#{name} for update;
5 rollback; --
6 -- rollback;

替换步骤5中name的值,观察结果:

name的值 执行结果
e 不阻塞
f 不阻塞
g 阻塞
h 不阻塞
i 阻塞
j 不阻塞

经过上面两条SQL语句的验证结果,咱们证实了咱们的g和i的锁定范围趋势为二者next-key叠加组合。

接下来咱们验证一下对辅助索引加锁后对聚合索引的锁转移,执行SQL语句的模板:

步骤 client 1 client 2
1 begin; --
2 SELECT * FROM user where name>'e' for update; --
3 -- SELECT * FROM user where id=#{id} for update;
5 rollback; --
6 -- rollback;

替换步骤5中id的值,观察结果:

id的值 执行结果
5 不阻塞
6 不阻塞
7 阻塞
8 不阻塞
9 阻塞
10 不阻塞

由结果可知对辅助索引name中的g和i列对应的聚合索引id列中的7和9加上了索引记录锁。

到目前为止全部实验结果和场景三彻底同样,这也很好理解,毕竟场景四和场景三只是辅助索引name的索引类型不一样,一个是惟一索引,一个是普通索引。

最后验证意向,next-key锁边界数据e,看看结论时候和场景三相同。

执行SQL语句的模板:

步骤 client 1 client 2
1 begin; --
2 SELECT * FROM user where name>'e' for update; --
3 -- begin;
4 -- INSERT INTO `user` (`id`, `name`,`age`) VALUES (#{id}, 'e','18');
5 rollback; --
6 -- rollback;

替换步骤5中id的值,观察结果:

id的值 执行结果
-1 不阻塞
1 不阻塞
2 不阻塞
3 不阻塞
4 不阻塞
5 不阻塞
6 不阻塞
7 阻塞
8 不阻塞
9 阻塞
10 不阻塞
11 不阻塞
12 不阻塞

注意7和9是索引记录锁记录锁

经过结果可知,当name列为索引记录上边界e时,并无对id有加锁行为,这点与场景三不一样。

对于惟一索引的范围查询和普通索引的范围查询相似,惟一不一样的是当辅助索引等于上下范围的边界值是不会对主键加上间隙锁。

惟一索引范围查询加锁范围:

  • 对于扫描的辅助索引记录的锁定范围就是多个索引记录next-key范围的叠加组合。
  • 对于聚合索引(主键)的锁定范围,会对多个辅助索引对应的聚合索引列加索引记录锁。

结论

InnoDB引擎会对他扫描过的索引记录加上相应的锁,经过“场景一”咱们已经明确了扫描一条普通索引记录的锁定范围,经过“场景三”咱们能够推断任意多个扫描普通索引索引记录的锁定范围。经过“场景二”咱们肯定了扫描一条惟一索引记录(或主键)的锁定范围。经过“场景四”咱们能够推断任意多个扫描索惟一引记录(或主键)的锁定范围。在实际的应用能够灵活使用,判断两条SQL语句是否相互锁定。这里还须要注意的是对于索引的查询条件,不能想固然的理解,他每每不是咱们理解的样子,须要结合执行计划判断索引最终扫描的记录数,不然会对加锁范围理解产生误差。




备注

注1:在事务隔离级别为SERIALIZABLE时,普通的select语句也会对语句执行过程当中扫描过的索引加上next-key锁。若是语句扫描的是惟一索引,那就将next-key锁降级为索引记录锁了。
注2:当更新语句修改聚合索引(主键)记录时,会对受影响的辅助索引执行隐性的加锁操做。当插入新的辅助索引记录以前执行重复检查扫描时和当插入新的辅助索引记录时,更新操做还对受影响的辅助索引记录添加共享锁。



参考:

https://dev.mysql.com/doc/ref...
https://dev.mysql.com/doc/ref...

相关文章
相关标签/搜索