您的位置:首页 > 游戏 > 游戏 > 如何管理网站内容_外贸网站建设 推广_友情链接交换网址大全_官方百度下载安装

如何管理网站内容_外贸网站建设 推广_友情链接交换网址大全_官方百度下载安装

2024/12/23 16:03:56 来源:https://blog.csdn.net/u011039332/article/details/132998313  浏览:    关键词:如何管理网站内容_外贸网站建设 推广_友情链接交换网址大全_官方百度下载安装
如何管理网站内容_外贸网站建设 推广_友情链接交换网址大全_官方百度下载安装

前言

我们这里主要是 来看一下 mysql 中的 间隙锁

间隙锁 主要存在的地方一般就是在 查询主键查询不到, 索引查询查询不到 的场景

然后 我们这里来调试一下 这里的整个流程, 间隙锁的加锁 以及 间隙锁的使用, 以及 间隙锁的释放

从逻辑上来说 间隙锁 锁定的是一个区间, 按照我们常规的理解 他应该会保存 区间的起始地址, 但是 从实际的实现层面 mysql 这边的实现相当巧妙, 它是挂在比目标值大的下一条记录上面的, 比如主键有 1, 5, 10, 我们这里执行查询 “select * from tz_test_04 where id = 7 for update;” 间隙锁是挂在 id 为 10 的这条记录上面的一个行锁, 并且 增加了一个 GAP_LOCK 的标志, 用于 业务的判断

根据这个目标记录 10, 以及 10 的上一条记录 5, 改间隙锁推导出锁定的区间就是 (5, 10)

 

我们这里测试表结构如下 

CREATE TABLE `tz_test_04` (`id` int(11) unsigned NOT NULL AUTO_INCREMENT,`field1` varchar(128) DEFAULT NULL,`field2` varchar(128) DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE,KEY `field_1_2` (`field1`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8

 

测试数据如下, 之所以 留下一些区间 是为了产生 “间隙”

2127be77d3df032de8284a066f4957c4.png

 

 

添加间隙锁

为了演示, 我们这里 事务1 加锁 sql 如下 

此查询会在 id 为 10 的行上面增加一个行锁, 并打上 GAP_LOCK 标志, 逻辑意义上这个间隙锁锁定的区间为 主键的 (5, 10)

因此 我们在这个区间去 增加记录 会获取 行排他锁, 获取失败, 如果是 更新或者删除, 还不到获取行排他锁锁的地方, 因此可以正常执行 

begin;
select * from tz_test_04 where id = 7 for update;
-- sleep10min
commit;

 

获取锁的地方如下, mode 为 515 = LOCK_GAP + LOCK_X, 间隙排他锁 

锁定的记录的 heap_no 为 5, 上下文还有它的 space, page 的相关信息 

5768252738c719e082726aabf30ed507.png

 

在 search_mvcc 中上下文如下, 是在 cmp_dtuple_is_prefix_of_rec 的判断内部, 条件匹配不上的时候, 会添加这个间隙锁

会进入这里的比较, 我这边目前知道的有 等于 比较 

我们再来看一下 rec 的记录信息 

f086afcd6d9b9c7ff621dbc0cf434fad.png

 

目标记录的上下文信息如下, 可以看到上一次更新是 事务39531 处理的, 当前事务是 39537, 然后对应的记录是 id 为 10 的记录 

2d3c0feb0bc1f55ea7177c4188ae535e.png

 

 

间隙锁的使用

间隙锁区间新增记录

然后在 事务2 执行sql 如下, 然后可以看到 产生了阻塞

INSERT INTO `test_02`.`tz_test_04`(`id`, `field1`, `field2`) VALUES (8, 'field1', '1');

这里是判断 上下文是否有 存在冲突的锁是否被其他事务持有, 锁住的事务是 事务39537 即上面那副截图的事务, 然后当前事务是 事务39538, 是由 mysql 这边新分配的一个事务 

然后期望锁定的目标记录是 5 号记录, 结合上面的信息来看 是 id 为 10 的记录 

持有锁的事务的 mode 为 547 为 LOCK_GAP + LOCK_RECORD + LOCK_X

当前事务尝试获取的 mode 为 2563 为 LOCK_INSERT_INTENSION + LOCK_GAP + LOCK_X

0f03a97f0dbd501347d9ec6384a9db28.png

 

然后这里 持有锁的上下文 和 尝试获取锁的上下文 是不兼容的, 因此 当前 事务 只能挂起, 等待目标 事务 释放间隙锁

45efc2092d2d4213568ddabaf64c3c6c.png

 

我们再来看一下 这里的 heap_no 为什么会是 id 为 10 的记录的 heap_no 呢?

获取锁的时候是在插入数据记录的时候, 获取当前记录的下一条记录, 比如我们这里插入的记录 id 为 8, 那么是应该放在 id 为 5 和 id 为 10 的记录之间, 下一条记录即为 id 为 10 的记录 

然后 因此就巧妙的实现了 间隙锁, 锁是加载一个行记录上面的, 但是实际锁定的却是一个区间

ca3fa42ea6690c899c29270716418af3.png

 

 

间隙锁区间更新记录 

然后在 事务2 执行sql 如下, 然后可以看到 不会产生阻塞

update tz_test_04 set field1 = 'fieldUpdated' where id = 8;

这是因为如下判断锁是否兼容的时候, 这里在 间隙锁的第二个 case, 这里满足, 就是持有锁的事务是间隙锁, 并且当前事务没有 LOCK_INSERT_INTENSION, 我们这里是更新, 是满足条件的, 因此 视为兼容

844c9f7a0d09b437720d50c4750cc0e0.png

 

 

间隙锁区间删除记录 

然后在 事务2 执行sql 如下, 然后可以看到 不会产生阻塞

delete from tz_test_04 where id = 8;

这是因为如下判断锁是否兼容的时候, 这里在 间隙锁的第二个 case, 这里满足, 就是持有锁的事务是间隙锁, 并且当前事务没有 LOCK_INSERT_INTENSION, 我们这里是删除, 是满足条件的, 因此 视为兼容

8f1c29608ab65f2408572ae710c17c2e.png

 

 

间隙锁的释放

释放同样是在 事务提交 的时候

c86f62f2c489aeca0c701a7d6b034c62.png

 

 

间隙锁锁索引的情况

tz_test_04 的数据表数据如下, 这里更新了一下 id 为 10 的记录的 field1 的值, 让其在字符串层面增量排序 

5e54970477cc64363b4adcd7e025d424.png

 

执行 sql 如下 

begin;
select * from tz_test_04 where field1 = 'field7' for update;
-- sleep10min
commit;

 

走的索引查询, 同样如果是 索引字段 field1 没有命中的情况, 会基于索引记录 添加间隙锁

从 rec, 或者代码所在区域 可以看出当前 rec 实际上时索引记录, 然后下面会走 查询是否匹配, 匹配的话走聚簇索引的回表查询, 这里我们的 事务是 事务39576

2920c15bedffd64ffd3961461e8991c0.png

 

执行 sql 如下 “INSERT INTO `test_02`.`tz_test_04`(`id`, `field1`, `field2`) VALUES (8, 'field8', '8');”

会添加 数据记录, 索引记录, 这里阻塞是阻塞在插入索引记录的地方 

当前 事务39577 尝试获取 “field8” 的下一个索引记录 “field9” 的间隙锁 mode 为 2563, LOCK_INSERT_INTENSION + LOCK_GAP + LOCK_X

发现 事务39576 持有 “field9” 的间隙锁 mode 为 547, LOCK_GAP + LOCK_RECORD + LOCK_X

然后当前事务 有LOCK_INSERT_INTENSION 和 已有的间隙锁不兼容, 因此当前 事务 需要等待

37749970c849618d17f74dea8795e073.png

 

 

间隙锁锁非索引记录的情况

执行 sql 如下 

begin;
select * from tz_test_04 where field2 = '7' for update;
-- sleep10min
commit;

 

这个效果就转换为了 在每一行记录上面 加上行临键锁

上下文在这里, 具体遍历了每一行记录, 以及 supremum 

b9d1f2fc425a0eb34f26c6ef413948ab.png

 

遍历的各个记录如下, 下图是在 row_search_mvcc 中查看的 rec 的记录信息, 可以通过 field1 字段观察出那一条记录 

505fba1bb49bc044c871c7e4fcd2e1c1.png

 

 

间隙锁锁最大值到正无穷的情况

间隙锁锁在主键上面

执行 sql 如下

begin;
select * from tz_test_04 where id = 15 for update;
-- sleep10min
commit;

 

这里是遍历到了 supremum 记录, 然后这里会在 supremum 记录上面添加一个 行排他锁 

但是它的作用和在 supremum 记录上面增加一个 间隙锁的效果 是一样的 

7e608f7ea00914ab9dd9aa9c16ab2d3a.png

 

然后执行 sql 如下 “INSERT INTO `test_02`.`tz_test_04`(`id`, `field1`, `field2`) VALUES (15, 'fieldc', 'c');”

然后这里可以看到是在添加数据记录的时候尝试获取锁 mode 为 2563, LOCK_INSERT_INTENSION + LOCK_GAP + LOCK_X

然后已经持有的事务 mode 为 LOCK_RECORD + LOCK_X

然后 两个事务的情况不在下面的四种兼容情况内, 因此 本事务需要阻塞

需要注意的是这里在 supremum 记录上面增加的是 行排他锁, 而不是 间隙锁

d5d6900653879cfbfc624f637d307b19.png

 

 

间隙锁锁在索引上面

执行 sql 如下 

begin;
INSERT INTO `test_02`.`tz_test_04`(`id`, `field1`, `field2`) VALUES (8, 'fieldc', 'c');
-- sleep10min
commit;

 

这里是遍历到了 supremum 记录, 然后这里会在 supremum 记录上面添加一个 行排他锁 

但是它的作用和在 supremum 记录上面增加一个 间隙锁的效果 是一样的 

82fc7b9844cd456c479d0b2c4b36752d.png

 

然后执行 sql 如下 “INSERT INTO `test_02`.`tz_test_04`(`id`, `field1`, `field2`) VALUES (8, 'fieldc', 'c');”

然后这里可以看到是在添加索引记录的时候尝试获取锁 mode 为 2563, LOCK_INSERT_INTENSION + LOCK_GAP + LOCK_X

然后已经持有的事务 mode 为 LOCK_RECORD + LOCK_X

然后 两个事务的情况不在下面的四种兼容情况内, 因此 本事务需要阻塞

需要注意的是这里在 supremum 记录上面增加的是 行排他锁, 而不是 间隙锁

d004b72a1d39c378fa0cd42fa65ccd8d.png

 

 

 

 

 

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com