是不是和第一次执行结果不同了,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'
的锁定范围:
索引记录g在name
列锁定范围为(e,g],(g,i)。索引记录i的在name
列锁定范围为(g,i],(i,+∞)。两者叠加后锁定范围为(e,g],(g,i],(i,+∞)。其中g,i为索引记录锁。
g和i对应id
列中的7和9加索引记录锁。
当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的锁定范围的叠加组合。
我们通过场景三验证了普通索引的范围查询语句加锁范围,现在我们来验证一下唯一索引的范围查询情况下的加锁范围。有了场景三的铺垫我们直接跳过扫描全部索引的情况,创建可以扫描范围记录的表结构并插入相应数据测试。
建表:
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引擎行级锁锁定范围详解的详细内容,更多请关注php中文网其它相关文章!
……