MySQL 核心模块揭秘 | 21 期 | 行锁 (1) 快速加锁

news2024/11/28 4:36:28

行锁有两种加锁逻辑,这一期我们聊聊其中之一的快速加锁。

作者:操盛春,爱可生技术专家,公众号『一树一溪』作者,专注于研究 MySQL 和 OceanBase 源码。

爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。

行锁有两种加锁逻辑,这一期我们聊聊其中之一的快速加锁。

本文基于 MySQL 8.0.32 源码,存储引擎为 InnoDB。

1. 两种加锁逻辑

更新、删除记录都需要加行锁,读取、插入记录有时候也需要加行锁,这意味着加行锁是个比较频繁的操作。

对于频繁的操作,为了性能着想,优化是件必须要做的事。

为此,InnoDB 把加行锁操作分为两种逻辑:快速加锁、慢速加锁。

每次加行锁,只要满足快速加锁条件,就会走快速加锁逻辑,提升加锁效率,不满足时,才走慢速加锁逻辑。

2. 先拿个令牌

前面我们介绍过锁模块结构,它有个 rec_hash 属性,是个哈希表,用于管理 InnoDB 中所有的行锁结构。

学习过哈希表的读者应该有所了解,哈希表通常使用数组作为底层数据结构,来管理加入其中的对象。

既然 rec_hash 是个哈希表,它自然也用数组来管理行锁结构了。

数组有多少个单元,由 InnoDB 的 buffer pool 大小和一个数据页大小共同决定。计算公式如下:

// srv_buf_pool_size 为 buffer pool 大小
// UNIV_PAGE_SIZE 为一个数据页大小
// 两者的单位都是字节
5 * (srv_buf_pool_size / UNIV_PAGE_SIZE)

用一句话来概括上面的公式,那就是 buffer pool 中能存放的数据页数量的 5 倍。

对于哈希表,有一个必须要解决的问题是哈希冲突

哈希冲突指的是加入哈希表的多个对象,有可能计算得到相同的哈希值,这种情况下,多个对象会映射到同一个数组单元。

rec_hash 解决冲突的方式,是把出现冲突的多个行锁结构串起来,形成一个链表。这样一来,出现冲突的那个数组单元,管理的就是包含多个行锁结构的链表,而不只是单个行锁结构。

没有出现冲突的数组单元怎么办?

它们各自管理的也是行锁结构链表,只是这些链表有点特殊,它们都只包含一个行锁结构。

每个数组单元都管理一个行锁结构链表,多个事务加行锁、事务提交或回滚时,如果同时读、写行锁结构链表,有可能会出现打架的情况。

,指的是遍历行锁结构链表。
,指的是把行锁结构加入链表,或者从链表中删除行锁结构。

为了避免多个事务打架,每个事务读、写行锁结构链表之前,都需要先拿到一个令牌,然后才能读、写行锁结构链表。

这里说的令牌是个稀缺物件,rec_hash 的每个数组单元只对应一个令牌。

多个事务读、写同一个数组单元管理的行锁结构链表,需要等待前面拿到令牌的事务把令牌还回去之后,等待中的事务才能按照先来后到的顺序拿到令牌。

前面介绍加表锁的流程时,我们说过 InnoDB 使用互斥量实现令牌。

加行锁的流程也同样使用互斥量来实现令牌。

InnoDB 为 rec_hash 的数组准备了 512 个互斥量,保存到一个名为 mutexes 的数组里。

每个事务读、写一个行锁结构链表之前,都需要获得这个链表对应的令牌,也就是 mutexes 数组中保存的某个互斥量。获得互斥量的步骤如下:

  • 根据数据页的页号、表空间 ID 计算得到哈希值。
  • 上一步的哈希值,对 rec_hash 的数组单元数量取模(哈希值 % 数组单元数量),得到一个结果。
  • 上一步的结果,对 mutexes 数组中保存的互斥量数量取模(上一步的结果 % 512),得到数组下标。
  • 根据上一步的数组下标,找到 mutexes 数组中对应的互斥量,然后申请获得这个互斥量(这一步可能需要等待)。

上面的第一个步骤,没来由的出现了数据页的页号表空间 ID,是不是有点莫名其妙?

别急,现在是时候介绍它们两个了。

前面说过,读行锁结构链表,指的就是遍历行锁结构链表。这个操作通常出现在加行锁之前。

每次加行锁,都只会对一条记录加锁。遍历行锁结构链表之前,需要获得互斥量,第一个步骤中数据页的页号、表空间 ID,指的就是加行锁的这条记录所属数据页的页号、表空间 ID。

写行锁结构链表,指的是把行锁结构加入链表,或者从链表中删除行锁结构。

每个行锁结构都有个 page_id 属性,里面保存了这个行锁结构对应数据页的页号、表空间 ID。

写行锁结构链表之前,需要获得互斥量,第一个步骤中数据页的页号、表空间 ID,指的就是要加入链表或者要从链表中删除的行锁结构的 page_id 属性中保存的数据页的页号、表空间 ID。

3. 再获取行锁结构

InnoDB 每次加行锁,都只会对一条记录加锁。

加行锁时,经过前面介绍的获得互斥量的步骤之后,接下来要做的,是看看 rec_hash 中有没有加锁记录所属数据页对应的锁结构。这个操作的流程如下。

第 1 步,根据加锁记录所属数据页的页号、表空间 ID 计算得到哈希值。

第 2 步,上一步的哈希值,对 rec_hash 的数组单元数量取模(哈希值 % 数组单元数量),得到 rec_hash 的数组下标。

第 3 步,根据上一步的数组下标,得到对应的行锁结构链表。

第 4 步,遍历行锁结构链表,每次取一个行锁结构,然后判断这个行锁结构对应的数据页,是否和加锁记录所属的数据页相同。

碰到满足这个条件的第一个行锁结构,就结束遍历,这个行锁结构作为本次遍历的结果。

当然了,还有一种可能,就是遍历完整个链表,都没有满足这个条件的行锁结构。

不管上面的流程有没有找到行锁结构,结果都非常重要,因为这个结果是 InnoDB 决定走快速加锁逻辑还是慢速加锁逻辑的关键。

4. 快速加锁之一

前面获取加锁记录所属数据页的第一个行锁结构,如果没有获取到,说明不会有任何事务阻塞本次加行锁操作,可以走快速加速逻辑。

因为没有获取到加锁记录所属数据页的行锁结构,也就没有可以共用的行锁结构,所以需要为加锁记录申请一个新的行锁结构。

和表锁结构一样,申请新的行锁结构也分为两种情况。

情况 1,使用预先创建的行锁结构。

每个事务对象初始化时,会创建 8 个行锁结构,供事务运行过程中加行锁使用。

事务加行锁需要新的锁结构时,只要预先创建的这些行锁结构,还有空闲的,就可以拿一个来使用。

如果预先创建的行锁结构都被已经被使用了,那就进入情况 2

情况 2,创建新的行锁结构。

创建过程的第一步是申请一块内存,这块内存分为两部分。

第一部分,用于存储行锁结构,大小为 96 字节。

第二部分,是 bitmap 内存区域,用于标识这个行锁结构对应的数据页中有哪些记录加了锁。这部分的大小由加锁记录所属数据页中的记录数量决定。具体计算逻辑如下:

static size_t lock_size(const page_t *page) {
  // 数据页中的记录数量
  ulint n_recs = page_dir_get_n_heap(page);

  /* Make lock bitmap bigger by a safety margin */
  return (1 + ((n_recs + LOCK_PAGE_BITMAP_MARGIN) / 8));
}

以上代码中,n_recs 为数据页中的记录数量,LOCK_PAGE_BITMAP_MARGIN 为 64。

以加锁记录所属数据页中包含 6 条记录为例,利用上面的公式来计算 bitmap 内存区域的大小。

// 70 整除 8,结果为 8
1 + ((6 + 64) / 8) = 1 + (70 / 8) = 1 + 8 = 9

关于 bitmap 内存区域,前面介绍行锁结构时,我们有过详细介绍,这里不再赘述。

分配内存之后,接下来要初始化行锁结构的各属性,以及 bitmap 内存区域。各属性中,需要重点介绍的是 type_mode

对于行锁结构,type_mode 属性的锁模式、锁类型、精确模式三个区域都会初始化。

type_mode 属性的第 1 ~ 4 位,是锁模式区域,本次加行锁的锁模式作为一个整数,写入这块区域中。

type_mode 属性的第 6 位会被设置为 1,表示这个锁结构对应的锁类型是行锁。

进入快速加锁逻辑,说明本次加行锁不需要等待,type_mode 属性的第 9 位不会被设置为 1。

上图是行锁结构的所有属性,除 type_mode 属性外,其它属性的初始化没有什么特殊的,不一一介绍了。

行锁结构的各属性初始化完成之后,就轮到 bitmap 内存区域了。

首先,这块内存区域的所有位都会被置为 0,然后本次加锁记录对应的位会被设置为 1。

后面的事务加行锁时,发现这个位被置为 1,就知道它对应的记录加了锁。

行锁结构的各属性、bitmap 内存区域初始化完成之后,就得到了一个已经初始化的行锁结构,接下来需要把它加入两个链表。

首先,把行锁结构加入 rec_hash 中某个数组单元对应的行锁结构链表,这个过程分为几个步骤:

  • 根据加锁记录所属数据页的页号、表空间 ID 计算得到哈希值。
  • 上一步的哈希值,对 rec_hash 的数组单元数量取模(哈希值 % 数组单元数量),得到 rec_hash 的数组下标。
  • 根据上一步的数组下标,得到对应的行锁结构链表,并返回链表中的第一个行锁结构。
  • 新的行锁结构放到第一个行锁结构之前,也就是加入链表的头部。

然后,新的行锁结构加入事务对象的 trx_locks 链表的尾部。

5. 快速加锁之二

前面获取加锁记录所属数据页的第一个行锁结构,如果获取到了,情况就复杂了一点点,因为既有可能走快速加锁逻辑,也有可能走慢速加锁逻辑。

如果这个锁结构命中了慢速加锁条件中的任何一个,就只能乖乖的走慢速加锁逻辑了,否则,也可以走快速加锁逻辑。

至于慢速加锁条件有哪些,先按下不表,稍后再说。

我们先来介绍第二种快速加锁逻辑,因为获取到了加锁记录所属数据页的第一个行锁结构,也就有了可以复用的行锁结构,本次加行锁不用再申请新的行锁结构。

因为不需要申请新的行锁结构,这种快速加锁逻辑比较简单,分为两种情况。

情况 1,如果获取到的行锁结构中,bitmap 内存区域对应本次加锁记录的位已经被设置为 1,说明当前事务已经对这条记录加了锁,本次加行锁的流程到此结束。

情况 2,如果获取到的行锁结构中,bitmap 内存区域对应本次加锁记录的位还是 0,那就把这个位设置为 1,本次加行锁的流程也就结束了。

6. 慢速加锁条件有哪些?

介绍第二种快速加锁逻辑时,我们提到了慢速加锁条件,现在是时候聊聊它们了。慢速加锁条件决定了走慢速加锁逻辑,还是走第二种快速加锁逻辑。

慢速加锁条件有四个,只要满足其中一个,就得乖乖的走慢速加锁逻辑。

条件 1,加锁记录所属的数据页,有两个或多个锁结构。这些锁结构有可能由一个或多个事务创建,这些事务可能包含,也可能不包含本次加锁的事务。

条件 2,前面获取到了加锁记录所属数据页的第一个行锁结构,但是这个锁结构不是当前事务创建的。

条件 3,获取到的第一个行锁结构的锁模式,和本次要加的行锁的锁模式不同。

条件 4,获取到的第一个行锁结构的 bitmap 内存区域空间不够,没有本次加锁记录对应的位,不能用于本次加锁操作。

7. 总结

快速条件的主要流程如下:

  • 获取加锁记录所属数据页对应的第一个行锁结构(不管这个行锁结构是当前事务创建的,还是其它事务创建的)。

  • 如果上一步没有获取到行锁结构,可以走第一种快速加锁逻辑。

    首先,需要申请一个新的行锁结构,并初始化行锁结构的各属性、bitmap 内存区域。

    然后,把行锁结构加入 rec_hash 中某个数组下标对应的行锁结构链表、当前事务对象的 trx_locks 链表。

  • 如果上一步获取到了行锁结构,并且没有命中任何一个慢速加锁条件,可以走第二种快速加锁逻辑。

    这种加锁逻辑很简单,只需要把行锁结构的 bitmap 内存区域中,本次加锁记录对应的位设置为 1(如果之前已经设置为 1,本次不需要重复操作)。

更多技术文章,请访问:https://opensource.actionsky.com/

关于 SQLE

SQLE 是一款全方位的 SQL 质量管理平台,覆盖开发至生产环境的 SQL 审核和管理。支持主流的开源、商业、国产数据库,为开发和运维提供流程自动化能力,提升上线效率,提高数据质量。

✨ Github:https://github.com/actiontech/sqle

📚 文档:https://actiontech.github.io/sqle-docs/

💻 官网:https://opensource.actionsky.com/sqle/

👥 微信群:请添加小助手加入 ActionOpenSource

🔗 商业支持:https://www.actionsky.com/sqle

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1848417.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【深海王国】小学生都能做的APP?AppInventor、BLE蓝牙、Arduino联合开发你的第一个手机远程控制程序(7)

Hi~ (o^^o)♪, 各位深海王国的同志们,早上下午晚上凌晨好呀~ 辛勤工作的你今天也辛苦啦(/≧ω) 今天大都督依旧为大家带来小学生都能学会的APP制作教程,帮你一周内快速开发一款可以和单片机无线通讯的手机蓝牙APP,let’s go! &a…

Python13 时间格式转换

在Python中,时间格式转换通常指的是将日期和时间数据从一种表示形式转换成另一种。这种转换经常使用Python的datetime和time模块来实现。这些模块提供了多种工具,可以帮助用户将时间表示为字符串、时间戳,或是更加结构化的datetime对象等多种…

Android-Framework:Handler全解析,看完这篇还不懂请给我寄刀片

//【1】拿到队列头部 Message p mMessages; boolean needWake; //【2】如果消息不需要延时&#xff0c;或者消息的执行时间比头部消息早&#xff0c;插到队列头部 if (p null || when 0 || when < p.when) { // New head, wake up the event queue if blocked. msg.next…

下班时间如何安排?

随着互联网的飞速发展和数字化时代的来临&#xff0c;越来越多的人开始探索除了主业以外的赚钱途径&#xff0c;以增加收入来源。本文将为您介绍几种当前热门的高薪副业项目&#xff0c;包括网络任务赚钱、开设个人网店、电商导购推广、在线辅导教学、技能变现服务、视频创作分…

探索C嘎嘎的奇妙世界:第十六关---STL(vector的练习)

1.只出现一次的数字 我们可以使用异或运算来解决这个问题&#xff1a; 异或运算有一个重要的性质&#xff1a;两个相同的数进行异或运算结果为 0&#xff0c;任何数与 0 异或结果为其本身。对于数组中的元素&#xff0c;依次进行异或运算&#xff0c;出现两次的元素异…

任务调度框架革新:TASKCTL在Docker环境中的高级应用

Docker&#xff1a;轻量级容器化技术的魅力 Docker 作为一款开源的轻量级容器化技术&#xff0c;近年来在 IT 界掀起了一股热潮。它通过封装应用及其运行环境&#xff0c;使得开发者可以快速构建、部署和运行应用。Docker 的优势在于其轻量级、可移植性和可扩展性&#xff0c;它…

店员顾客起纠纷?EasyCVR+AI视频监控管理平台,助力连锁门店安全运营

近日&#xff0c;某品牌咖啡店店员与顾客起冲突登上了新闻热搜&#xff0c;一时间引发大量关注。随着门店完整的监控视频录像公开&#xff0c;大家才了解事情的原委&#xff0c;而并非网传的那样。 随着社会的进步和科技的发展&#xff0c;视频监控已成为各行各业不可或缺的安全…

教你开发一个适合外贸的消息群发工具!

在全球化日益加速的今天&#xff0c;外贸业务已经成为许多企业不可或缺的一部分&#xff0c;而在外贸业务中&#xff0c;高效的消息群发工具则扮演着至关重要的角色。 它能够帮助企业快速、准确地传达产品信息、促销活动等重要内容&#xff0c;从而提升业务效率和客户满意度&a…

vben admin BasicTable表格基本使用

vben admin是一款强大的后台管理系统&#xff0c;广泛应用于各种项目中。本文将为您详细介绍如何使用 便您更快地上手并充分发挥其功能。 Table 表格 | Vben Admin一个开箱即用的前端框架https://jeesite.com/front/vben-admin/docs/components/table.html#usage 1.register:…

用类来实现输入和输出时间(时:分:秒)

编写程序&#xff1a; 运行结果&#xff1a; 程序分析&#xff1a; 这是一个很简单的例子。类Time中只有数据成员&#xff0c;而且它们被定义为公用的&#xff0c;因此可以在类的外面对这些成员进行操作。t1被定义为Time类的对象。在主函数中向t1对象的数据成员输入用户…

【ai】tx2-nx:Yolo V4 直接安装与 测试

Yolo V4环境搭建 git clone https://github.com/AlexeyAB/darknet.gitcuda版本和路径也要改成我们的实际版本和路径,否则会编译失败 编译 sudo make nvidia@tx2-nx:~/twork/02_yolov4/darknet$ vi Makefile nvidia@tx2-nx:~/twork/02_yolov4/darknet$ sudo make [sudo

从起心动念上,做个好人,好人好自己(阳明心学)

人心中的天理终究不会泯灭&#xff0c;每个人心中本来就有良知&#xff0c;良知是与生俱来的。 起心动念决定了意识、语言和行为&#xff0c;而这些意识、语言和行为又决定了事事物物&#xff0c;事事物物连接起来就是我们的人生。 让自己不断产生更好的念头&#xff0c;坚持…

拿下PostgreSQL中级认证PCP,现在它是我简历上的亮点了!

作者&#xff1a;IT邦德 中国DBA联盟(ACDU)成员&#xff0c;10余年DBA工作经验&#xff0c; Oracle、PostgreSQL ACE CSDN博客专家及B站知名UP主&#xff0c;全网粉丝10万 擅长主流Oracle、MySQL、PG、高斯及Greenplum备份恢复&#xff0c; 安装迁移&#xff0c;性能优化、故障…

小红书点赞评论收藏【更新版本】

小红书作为社交媒体的一个亮点&#xff0c;其点赞、评论和收藏的功能形成了一种特有的交流机制。点赞简直就是一枚迷你奖章&#xff0c;为创作者带去信心与动力。一次点赞&#xff0c;表达的是你心中无言的喜好和认可&#xff1b;它如明亮的灯塔&#xff0c;在汪洋大海中为创作…

史上最全的整合Harbor安装教程,哈哈哈哈

一、安装docker 下载地址&#xff1a;https://download.docker.com/linux/static/stable/x86_64/docker-23.0.4.tgz 1.1 解压二进制包 wget https://download.docker.com/linux/static/stable/x86_64/docker-23.0.4.tgz tar zxvf docker-23.0.4.tgz mv docker/* /usr/bin1.2…

JavaWeb阶段学习知识点(一)

【参考视频】https://www.bilibili.com/video/BV1m84y1w7Tb?p=167&vd_source=38a16daddd38b4b4d4536e9c389e197f SpringBoot项目的创建和接口配置 做一个springboot项目,从创建项目到实现浏览器访问localhost:8080/hello返回字符串hello world的全流程 1. 创建项目 idea新…

2-12 基于CV模型卡尔曼滤波、CT模型卡尔曼滤波、IMM模型滤波的目标跟踪

基于CV模型卡尔曼滤波、CT模型卡尔曼滤波、IMM模型滤波的目标跟踪。输出跟踪轨迹及其误差。程序已调通&#xff0c;可直接运行。 2-12 CV模型卡尔曼滤波 CT模型卡尔曼滤波 - 小红书 (xiaohongshu.com)

memory动态内存管理学习之shared_ptr

此头文件是动态内存管理库的一部分。std::shared_ptr 是一种通过指针保持对象共享所有权的智能指针。多个 shared_ptr 对象可持有同一对象。下列情况之一出现时销毁对象并解分配其内存&#xff1a; 最后剩下的持有对象的 shared_ptr 被销毁&#xff1b;最后剩下的持有对象的 s…

用Vite基于Vue3+ts+DataV+ECharts开发数据可视化大屏,即能快速开发又能保证屏幕适配

数据可视化大屏 基于 Vue3、Typescript、DataV、ECharts5 框架的大数据可视化&#xff08;大屏展示&#xff09;开发。此项目vue3实现界面&#xff0c;采用新版动态屏幕适配方案&#xff0c;全局渲染组件封装&#xff0c;支持数据动态刷新渲染、内部DataV、ECharts图表都支持自…

cad批量打印pdf怎么弄?介绍三种打印方法

cad批量打印pdf怎么弄&#xff1f;在CAD设计领域&#xff0c;批量打印PDF文件是一项常见且至关重要的任务。面对大量的CAD图纸&#xff0c;如何高效地进行转换和打印&#xff0c;成为了设计师们亟待解决的问题。今天&#xff0c;我们就来推荐三款能够批量打印PDF的CAD软件&…