目录
背景知识
1. InnoDB锁的类型
1.1 Shared and Exclusive Locks
1.2 Record Locks、Gap Locks、Next-Key Locks
2. 加锁语句
SQL语句验证
1.测试环境
2.验证场景
2.1 场景一
2.2 场景二
2.3 场景三
2.4 场景四
结论
备注
首页 数据库 mysql教程 MySQL数据库InnoDB引擎行级锁锁定范围详解

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

Feb 23, 2018 pm 01:49 PM
innodb mysql 数据库

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

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

  • 什么样的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表示锁冲突,兼容不被阻塞,冲突被阻塞。由表可知一旦一个事务加了排他锁,其他个事务加任何锁都需要等待。多个共享锁不会相互阻塞。

1.2 Record Locks、Gap Locks、Next-Key Locks

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

以下定义摘自MySQL官方文档

  • 记录锁(Record Locks):记录锁锁定索引中一条记录。

  • 间隙锁(Gap Locks):间隙锁要么锁住索引记录中间的值,要么锁住第一个索引记录前面的值或者最后一个索引记录后面的值。

  • Next-Key Locks:Next-Key锁是索引记录上的记录锁和在索引记录之前的间隙锁的组合。

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

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

  • 记录锁的锁定范围是单独的索引记录,就是3、5、8、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:当更新语句修改聚合索引(主键)记录时,会对受影响的辅助索引执行隐性的加锁操作。当插入新的辅助索引记录之前执行重复检查扫描时和当插入新的辅助索引记录时,更新操作还对受影响的辅助索引记录添加共享锁。

相关推荐:

mysql执行sql文件报错Error: Unknown storage engine‘InnoDB如何解决

MySQL启动时InnoDB引擎被禁用了怎么办

MySQL存储引擎MyISAM和InnoDB之间的比较


以上是MySQL数据库InnoDB引擎行级锁锁定范围详解的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
2 周前 By 尊渡假赌尊渡假赌尊渡假赌
仓库:如何复兴队友
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒险:如何获得巨型种子
4 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

如何优化 PHP 中的 MySQL 查询性能? 如何优化 PHP 中的 MySQL 查询性能? Jun 03, 2024 pm 08:11 PM

可以通过以下方式优化MySQL查询性能:建立索引,将查找时间从线性复杂度降至对数复杂度。使用PreparedStatements,防止SQL注入并提高查询性能。限制查询结果,减少服务器处理的数据量。优化连接查询,包括使用适当的连接类型、创建索引和考虑使用子查询。分析查询,识别瓶颈;使用缓存,减少数据库负载;优化PHP代码,尽量减少开销。

如何在 PHP 中使用 MySQL 备份和还原? 如何在 PHP 中使用 MySQL 备份和还原? Jun 03, 2024 pm 12:19 PM

在PHP中备份和还原MySQL数据库可通过以下步骤实现:备份数据库:使用mysqldump命令转储数据库为SQL文件。还原数据库:使用mysql命令从SQL文件还原数据库。

如何使用 PHP 插入数据到 MySQL 表中? 如何使用 PHP 插入数据到 MySQL 表中? Jun 02, 2024 pm 02:26 PM

如何将数据插入MySQL表中?连接到数据库:使用mysqli建立与数据库的连接。准备SQL查询:编写一个INSERT语句以指定要插入的列和值。执行查询:使用query()方法执行插入查询,如果成功,将输出一条确认消息。

如何修复 MySQL 8.4 上的 mysql_native_password 未加载错误 如何修复 MySQL 8.4 上的 mysql_native_password 未加载错误 Dec 09, 2024 am 11:42 AM

MySQL 8.4(截至 2024 年的最新 LTS 版本)中引入的主要变化之一是默认情况下不再启用“MySQL 本机密码”插件。此外,MySQL 9.0完全删除了这个插件。 此更改会影响 PHP 和其他应用程序

如何在 PHP 中使用 MySQL 存储过程? 如何在 PHP 中使用 MySQL 存储过程? Jun 02, 2024 pm 02:13 PM

要在PHP中使用MySQL存储过程:使用PDO或MySQLi扩展连接到MySQL数据库。准备调用存储过程的语句。执行存储过程。处理结果集(如果存储过程返回结果)。关闭数据库连接。

如何使用 PHP 创建 MySQL 表? 如何使用 PHP 创建 MySQL 表? Jun 04, 2024 pm 01:57 PM

使用PHP创建MySQL表需要以下步骤:连接到数据库。创建数据库(如果不存在)。选择数据库。创建表。执行查询。关闭连接。

在PHP中使用MySQLi建立数据库连接的详尽教程 在PHP中使用MySQLi建立数据库连接的详尽教程 Jun 04, 2024 pm 01:42 PM

如何在PHP中使用MySQLi建立数据库连接:包含MySQLi扩展(require_once)创建连接函数(functionconnect_to_db)调用连接函数($conn=connect_to_db())执行查询($result=$conn->query())关闭连接($conn->close())

iOS 18 新增'已恢复”相册功能 可找回丢失或损坏的照片 iOS 18 新增'已恢复”相册功能 可找回丢失或损坏的照片 Jul 18, 2024 am 05:48 AM

苹果公司最新发布的iOS18、iPadOS18以及macOSSequoia系统为Photos应用增添了一项重要功能,旨在帮助用户轻松恢复因各种原因丢失或损坏的照片和视频。这项新功能在Photos应用的"工具"部分引入了一个名为"已恢复"的相册,当用户设备中存在未纳入其照片库的图片或视频时,该相册将自动显示。"已恢复"相册的出现为因数据库损坏、相机应用未正确保存至照片库或第三方应用管理照片库时照片和视频丢失提供了解决方案。用户只需简单几步

See all articles