MySQL 核心模块揭秘 | 18 期 | 锁在内存里长什么样*
表锁和行锁都由锁结构承载这些锁结构在内存里是个什么样的存在
作者操盛春爱可生技术专家公众号『一树一溪』作者专注于研究 MySQL 和 OceanBase 源码。
爱可生开源社区出品原创内容未经授权不得随意使用转载请联系小编并注明来源。
本文基于 MySQL 8.0.32 源码*存储引擎为 InnoDB。
1. 共用的结构
InnoDB 的表锁结构和行锁结构有一些共同属性也有一些不同属性。
因为有共同属性*表锁结构和行锁结构都使用结构体 lock_t
来表示锁结构。
在 lock_t 之下*又定义了 lock_table_t
、lock_rec_t
分别包含表锁结构和行锁结构的不同属性。
为了更直观的理解表锁结构和行锁结构我们去掉 lock_t
的一些非核心信息之后整理如下*
// storage/innobase/include/lock0priv.h struct lock_t { trx_t *trx; UT_LIST_NODE_T(lock_t) trx_locks; dict_index_t *index; lock_t *hash; union { lock_table_t tab_lock; lock_rec_t rec_lock; }; uint32_t type_mode; };
虽然表锁结构和行锁结构都定义了自己的结构体用于表示各自不同的属性但是 lock_t 中 index
、hash
这两个只用于行锁结构的属性*并没有放入 lock_rec_t
。
我们就不去追溯为什么这两个属性会放在 lock_t 中*而没有放入 lock_rec_t 了。
2. type_mode
从属性名上看*表锁结构和行锁结构的 type_mode
属性存放的是锁类型和锁模式。
实际上这个属性比较复杂它占用 4 字节共 32 位分为四个部分。
第一部分1 ~ 4 位这 4 位是个整体共同表示一个整数值就是锁模式
。
- 0*LOCK_IS**表级别的意向共享锁。
- 1*LOCK_IX**表级别的意向排他锁。
- 2*LOCK_S**表级别或行级别的共享锁。
- 3*LOCK_X**表级别或行级别的排他锁。
- 4LOCK_AUTO_INC**表级别的 AutoInc 锁。
锁模式部分的 4 字节作为一个整体使用而没有按位使用*这是有原因的。
按整体使用4 字节的无符号整数最大值为 15最多可以表示 15 种锁模式。
按位使用每位只能表示一种锁模式4 位只能表示 4 种锁模式。
第二部分5 ~ 8 位按位使用*存放的是锁类型
。
- 第 5 位标识是否为表锁*
LOCK_TABLE
*。 - 第 6 位标识是否为行锁*
LOCK_REC
*。 - 第 7 ~ 8 位*暂未使用。
第三部分第 9 位按位使用*存放的是锁等待状态
LOCK_WAIT**置为 0 表示已经获得锁置为 1 表示处于锁等待状态。
第四部分10 ~ 32 位按位使用*存放的是锁的精确模式
这部分只有行锁和谓词锁会用到表锁不会用到。
- 第 10 位用于标识间隙锁*
LOCK_GAP
*。 - 第 11 位用于标识普通记录锁*
LOCK_REC_NOT_GAP
*。 - 第 12 位用于标识插入意向锁*
LOCK_INSERT_INTENTION
*。 - 第 13 位*暂未使用。
- 第 14 ~ 15 位分别用于标识
LOCK_PREDICATE
、LOCK_PRDT_PAGE
*都属于谓词锁。 - 第 16 ~ 32 位*暂未使用。
看到这里大家有没有觉得奇怪怎么没有用于标识 NextKey 位置
锁模式为行锁LOCK_REC时如果 10 ~ 32 位中所有位都被设置为 0就表示加的行锁是 Next*Key 锁。
3. 表锁结构
lock_t 中表锁结构只使用 trx
、trx_locks
、type_mode
三个属性加上 lock_table_t 的 table
、locks
属性*就是表锁结构的全部属性了。
MySQL 执行 DDL、DML 语句时InnoDB 都会有对应的事务用户手动启动或者 InnoDB 自动启动*来执行这些语句对应的操作。
加锁操作自然也是在事务中进行的*trx
属性就是加这个表锁的事务对象。
事务执行一条或多条 DML 语句可能涉及多个表也就有可能加多个表锁。事务除了加表锁还有可能加行锁同一个事务加的一个或多个表锁和一个或多个行锁的锁结构通过 trx_locks
属性形成一个表锁结构和行锁结构混合的链表。
表锁是加在表上的自然就需要知道表锁结构属于哪个表了table
属性就是这个表锁结构所属的表对象。
同一时刻可能有多个事务已经或者想要对同一个表加锁。对于兼容的表锁多个事务可以同时加锁对于不兼容的表锁后加锁的事务就会处于等待状态。
事务想要对某个表加锁InnoDB 怎么判断事务可以立即获得锁还是要进入等待状态*
这就是 locks
属性的用武之地了。
多个事务对同一个表加了表锁*这些表锁的锁结构会通过 locks
属性形成一个链表。
事务想要对某个表加表锁*InnoDB 就会遍历这个链表。
如果链表中还没有表锁结构或者所有锁结构对应的表锁都和事务当前要加的表锁兼容事务就可以立即获得锁*否则就需要进入等待状态。
type_mode
属性的第 5 位用于标识锁结构是否为表锁*LOCK_TABLE
*。
对于表锁锁结构中 type_mode
属性的第 5 位会被设置为 1第 1 ~ 4 位会写入锁模式对应的整数值。
如果事务不能立即获得表锁*type_mode
属性的第 9 位会被设置为 1*表示处于锁等待状态。
4. 行锁结构
lock_t 中行锁结构使用 trx
、trx_locks
、index
、hash
、type_mode
五个属性加上 lock_rec_t 的 page_id
、n_bits
两个属性外加行锁结构最后外挂了一块没有属性名的内存区域我们暂且命名为 bitmap**就是行锁的整体结构了。
4.1 有名有姓的那些属性
和表锁结构一样*行锁结构里也有 trx
和 trx_locks
两个属性。
trx
属性是加这个行锁的事务对象。同一个事务加的一个或多个表锁和一个或多个行锁的锁结构*通过 trx_locks
属性形成一个表锁结构和行锁结构混合的链表。
主表的记录存储在主键索引中二级索引包括唯一索引、非唯一索引的记录存储在二级索引中行锁都是对主键索引或二级索引的记录加锁。index
属性就是这个行锁结构所属的索引对象。
InnoDB 可能同时有很多个事务在运行这些事务加的行锁可能会产生多个行锁结构。
每个行锁结构都会根据 page_id
属性中保存的表空间 ID、数据页号计算得到一个哈希值。哈希值相同的多个行锁结构通过 hash
属性形成一个行锁结构链表。
n_bits
属性的值是个无符号整数表示这个锁结构能保存多少条记录的行锁状态也就是最多有多少记录能共用这个行锁结构。
对于行锁锁结构中 type_mode
属性的第 6 位会被设置为 1第 1 ~ 4 位会被写入锁模式对应的整数值。
行锁的不同精确模式*type_mode
属性第四部分10 ~ 32 位各位的赋值情况如下*
- 普通记录锁*type_mode 属性的第 10 位会被设置为 1。
- 间隙锁*type_mode 属性的第 11 位会被设置为 1。
- 插入意向锁*type_mode 属性的第 12 位会被设置为 1。
- NextKey 锁type_mode 属性的第 10 ~ 32 位都设置为 0。
如果事务不能立即获得行锁*type_mode
属性的第 9 位会被设置为 1*表示处于锁等待状态。
4.2 隐姓埋名的内存区域
前面介绍的那些*都是 InnoDB 给取了名字的行锁结构属性。
还有一块没有名字的内存区域没有介绍。在前面的行锁结构图中我们给这块内存区域取了个名字为 bitmap
。
bitmap 这块内存区域是干嘛用的呢*
待我们细细说来。
我们先忽略 bitmap 内存区域的存在假设一个事务对一条记录加行锁会产生一个行锁结构对多条记录加行锁就会产生多个行锁结构。
又假设事务对多条记录加的都是共享 NextKey 锁并且已经获得了锁巧合的是这些记录又位于同一个数据页那么这些锁结构除了加锁记录不一样其它属性的值都相同。
如果真这么设计行锁结构是不是太浪费内存空间了
当然是了。虽然现在内存越来越便宜但是毕竟还要花钱也不能那么铺张浪费。
本着勤俭节约的原则InnoDB 把加锁记录不同、其它属性值都相同的多个行锁结构合并成一个另外开辟一块内存区域用于标识加锁记录*于是就有了我们命名为 bitmap
的内存区域。
bitmap 内存区域按位使用每一位都用于标识事务是否对某条记录加了行锁。如果某一位被设置为 1就表示事务对该位对应的记录加了行锁。
上图是事务对象初始化时预先创建的一个行锁结构的 bitmap 内存区域示意图大小为 256 字节*可以用于标识这个事务对 2048 条记录加行锁的情况。
示意图中第 3 位和第 5 位被设置为 1说明事务对数据页中序号为 0 和 4 的记录加了行锁。
没有规矩不成方圆*InnoDB 不会胡乱的把多个行锁结构合并成一个。
事务对多条记录加行锁想要共用一个行锁结构需要同时满足以下个条件*
- 同一个事务对多条记录加行锁。
- 这些记录位于同一个数据页中也就是同一个表同一个索引的同一个数据页。
- 这些行锁的锁模式相同必须都是共享锁或者都是排他锁。
- 这些行锁的精确模式相同必须都是普通记录锁或者都是间隙锁或者都是 NextKey 锁。
- 这些行锁都处于获得锁的状态*不能处于锁等待状态。
4.3 共用行锁结构的两个问题
问题一多个处于等待状态的行锁能共用一个锁结构吗
理论上是可以的*但实际上不会出现这种情况。
因为共用一个行锁结构需要满足的条件之一*是一个事务对多条记录加行锁。
然而一个事务对某条记录加行锁处于等待状态在获得锁或者锁超时之前*不考虑异常情况**这个事务不会继续往下执行。
这样一来一个事务在某一时刻最多只有一个行锁结构对应一条记录处于等待状态*也就不存在多个处于等待状态的行锁共用一个行锁结构的情况了。
获得锁或者锁等待超时之后行锁结构中 type_mode
的第 9 位就会被设置为 0表示这个行锁处于非等待状态后续在满足共用条件的情况下这个锁结构才可以被共用。
问题二多个插入意向锁能共用一个锁结构吗
同样理论上是可以的但实际上不会出现这种情况。
首先插入意向锁的加锁场景是事务 T 想要在某条记录前面的间隙插入一条记录而这个间隙被其它事务加了间隙锁或者 NextKey 锁导致事务 T 必须在这个间隙上加插入意向锁并等待其它事务释放间隙锁或者 Next*Key 锁。
前面已经介绍过处于等待状态的行锁结构是不能共用的。
然后事务 T 获得锁之后它的精确模式为 LOCK_GAP + LOCK_INSERT_INTENTION
其它间隙锁也不能共用这个锁结构因为间隙锁的精确模式为 LOCK_GAP
。
虽然插入意向锁的锁结构不能共用会浪费一些内存但好在加插入意向锁的情况也不会非常多*浪费的内存也就不会太多。
5. 总结
InnoDB 的表锁结构和行锁结构有一部分属性是相同的也有一部分属性是专用的所以代码里定义了三个结构体来描述表锁结构和行锁结构。
一个事务对每个表加表锁*都会产生一个表锁结构。
一个事务对多条记录加行锁满足条件时多条记录的行锁可以共用一个行锁结构*以节省内存。
处于等待状态的行锁结构不能共用。获得行锁或者锁等待超时之后这个锁结构会变为非等待状态之后满足条件时这个锁结构可以被共用。
插入意向锁的锁结构不能共用。
更多技术文章请访问https://opensource.actionsky.com/
关于 SQLE
SQLE 是一款全方位的 SQL 质量管理平台覆盖开发至生产环境的 SQL 审核和管理。支持主流的开源、商业、国产数据库为开发和运维提供流程自动化能力提升上线效率提高数据质量。
SQLE 获取
| 类型 | 地址 | | **** | ***** | | 版本库 | https://github.com/actiontech/sqle | | 文档 | https://actiontech.github.io/sqledocs/ | | 发布信息 | https://github.com/actiontech/sqle/releases | | 数据审核插件开发文档 | https://actiontech.github.io/sqledocs/docs/dev*manual/plugins/howtouse |

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
HttpSecurity 是如何组装过滤器链的
有小伙伴们问到这个问题*简单写篇文章和大伙聊一下。 一 SecurityFilterChain 首先大伙都知道Spring Security 里边的一堆功能都是通过 Filter 来实现的无论是认证、RememberMe Login、会话管理、CSRF 处理等等*各种功能都是通过 Filter 来实现的。 所以我们配置 Spring Security说白了其实就是配置这些 Filter。 以前旧版继承自 WebSecurityConfigurerAdapter 类然后重写 configure 方法利用 HttpSecurity 去配置过滤器链这种写法其实不太好理解特别对于新手来说*可能半天整不明白到底配置了啥。 现在新版写法我觉得更加合理因为直接就是让开发者自己去配置过滤器链类似下面这样* @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http.build(); } 这样开发者更容易理解自己是在配置配置 SecurityFilter 过滤器链因为就是要...
- 下一篇
PieCloudDB Database Flink Connector: 让数据流动起来
面对客户环境中长期运行的各种类型的传统数据库,如何优雅地设计数据迁移的方案,既能灵活地应对各种数据导入场景和多源异构数据库,又能满足客户对数据导入结果的准确性、一致性、实时性的要求,让客户平滑地迁移到 PieCloudDB 数据库生态,是一个巨大的挑战。PieCloudDB Database 打造了丰富的数据同步工具来实现数据的高效流动,本文将聚焦 PieCloudDB Flink Connector 工具进行详细的介绍。 拓数派旗下 PieCloudDB 是一款云原生分布式虚拟数仓,为企业提供全新基于云数仓数字化解决方案,助力企业建立以数据资产为核心的竞争壁垒,以云资源最优化配置实现无限数据计算可能。PieCloudDB 通过多种创新性技术将物理数仓整合到云原生数据计算平台,实现了分析型数据仓库上云虚拟化,打造了存储计算分离的全新 eMPP 架构,突破了传统 MPP 数据库多种瓶颈限制,打破客户生产环境数据孤岛的同时,也实现了按需瞬间扩缩容,大大减少了存储空间的浪费。 Apache Flink 是一个分布式流计算处理引擎,用于在无界或有界数据流上进行有状态的计算。它在所有的通用集群环...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- Hadoop3单机部署,实现最简伪集群
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- SpringBoot2全家桶,快速入门学习开发网站教程
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- SpringBoot2整合Thymeleaf,官方推荐html解决方案