【innodb阅读笔记】之 行格式(Dynamic)

news/2024/12/18 19:24:00/

一、背景

        Innodb 1.0 版本开始引入了新的行格式 dynamic,新的行格式在存放 blob 中的数据采用了完全行溢出的方式,在数据页中只存放 20 字节的指针,实际数据都存放在 Off page 中,而 compact 会存放 768 个前缀字节。

二、dynamic 行记录格式

        数据区在数据⾏中的位置如下图所⽰:

        从图中可以观察到,dynamic 行记录格式的首部是一个非 null 变长字段长度列表,并且是按照列的逆序放置的,其长度为:

        若列的长度小于255字节,用 1 字节表示

        若大于255个字节,则用 2 字节表示

        边长字段的长度最大不能超过两个字节,这是因为 mysql 数据库中 varchar 类型的最大长度限制为 65535。

        变长字段之后的第二个部分是 null 标识位,该标识指示了该行数据是否有 null 值,有则用 1 表示,该部分所占字节应该为 1 字节。如果创建表结构数据列都指定为非空,则 null 标识位省略。

        接下来的部分是记录头信息,固定占用 5 字节,每位含义如下:

Dynamic 记录头信息
名称大小(bit)描述
()1 预留字节
()1 预留字节
deleted_flag1该行是否已被删除
min_rec_flag1存储目录项记录中主键值最小的目录项记录置为 1,其它情况都置0.
n_owned4页目录中每个组的最后一条记录会存储该组的记录数,作为 n_owned 字段。值的关注的是,在mysql中最小记录是一组,普通记录与其它记录是一组,因此最小记录中n_owned 属性是1,最大记录的 n_owned 值是5.
heap_no13当前页中该记录的排序位置
record_type3记录类型 0 表示普通类型,1 表示B+树的非叶子节点 2 表示 最小记录,3 表示 最大记录。
next_record16页中 下一条记录的相对位置
total40 合计

从分隔线向右第⼀个字段存储真实数据的主键值,对于主键值有以下⼏种情况:

        如果表中定义了主键,则直接存储主键的值;

        如果是复合主键会根据列定义的顺序依次排列在这⾥;

        如果没有主键,会优先使⽤第⼀个不允许为NULL的 UNIQUE 唯⼀列作为主键;

        如果既没有主键也没有唯⼀键,那么InnoDB会构建⼀个6字节的字段 DB_ROW_ID 作为⾏的唯⼀标识,存储在真实数据的头部 

紧接着是在事务运⾏中两个⾮常重要的固定字段,

        6 字节的事务ID字段 DB_TX_ID ,记录创建或最后⼀次修改该记录的事务ID

        7 字节的回滚指针字段 DB_ROLL_PTR ,如果在事务中这条记录被修改,指向这条记录的上⼀个版本

        接下来就是除了主键和值为NULL的列之外,其他列的真实数据,按照顺序从左到右依次排列

        ⾄于为什么不存储 NULL 值,原因很简单,就是为了节少空间,所有允许为 NULL 的列都会在⾏额外信息区的 NULL 值列表中进⾏标识。

三、实践

# 创建表结构
mysql> create table mytest (->  t1 varchar(10),->  t2 varchar(10),->  t3 char(10),->  t4 VARCHAR(10)-> ) engine=innodb charset=latin1 row_format = dynamic;
Query OK, 0 rows affected (0.03 sec)# 插入数据
mysql> INSERT INTO mytest VALUES ('a', 'bb', 'bb', 'ccc');
Query OK, 1 row affected (0.01 sec)
mysql> INSERT INTO mytest VALUES ('d', 'ee', 'ee', 'fff');
Query OK, 1 row affected (0.00 sec)
mysql> insert into mytest values ('d', null, null, 'fff');
Query OK, 1 row affected (0.00 sec)# 用 notepad++ 打开 mytest.ibd 文件,需要下载 hex-editor 插件, 定位到C078位置
03 02 01                      # 变长字段列表 逆序
00                            # null 标识位
00 00 10 00 2c                # record header 
00 00 00 00 02 01             # rowid
00 00 00 00 05 56             # transactionid
d1 00 00 01 50 01 10          # roll pointer
61                            # 第一列数据
62 62                         # 第二列数据
62 62 20 20 20 20 20 20 20 20 # 第三列数据
63 63 63                      # 第四列数据# 第一行 变长字段列表为逆序状态,转换回来为01 02 03,
#     对应第一列、第二列、第4列长度分别为、1字节、2字节、3字节# 第二行 null 标识位 目前没有null的列 所以为 00# 第三行为 record header 占用5字节
#    第1个字节转换为二进制 0 0 0 0 0 0 0 0
#        预留字段 0 
#        预留字段 0
#        deleted_flag 0 该行 未删除
#        min_rec_flag 0 该行不是索引列
#        n_owned      0000  该组拥有的记录数 不记录在当前节点
#    第 2 个和第 3 个字节转为二进制 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
#        heap_no 0 0 0 0 0 0 0 0 0 0 0 1 0 排在第一行数据
#        record_type 0 0 0 表示普通记录
#    第3个和第5个字节 next_record 002C 下一行的记录为当前位置 + 002C
#        C080 + 002C = C0AC 下一行数据的next_record位置# 第二行数据
03 02 01                      # 边长字段列表 逆序
00                            # null 标识位
00 00 18 00 2b                # record header 
00 00 00 00 02 02             # rowid
00 00 00 00 05 57             # transactionid
d2 00 00 01 51 01 10          # roll pointer
64                            # 第一列数据
65 65                         # 第二列数据 
65 65 20 20 20 20 20 20 20 20 # 第三列数据
66 66 66                      # 第四列数据
# 第一行 变长字段列表为逆序状态,转换回来为01 02 03,
#     对应第一列、第二列、第4列长度分别为、1字节、2字节、3字节# 第二行 null 标识位 目前没有null的列 所以为 00# 第三行为 record header 占用5字节
#    第1个字节转换为二进制 0 0 0 0 0 0 0 0
#        未知 0 
#        未知 0
#        deleted_flag 0 改行未删除
#        min_rec_flag 0 该行不是最小记录
#        n_owned      0000  该组拥有的记录数 不记录在当前节点
#    第2个和第3个字节转为二进制 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0
#        heap_no 0 0 0 0 0 0 0 0 0 0 0 1 1 排在第二行数据
#        record_type 0 0 0 表示普通记录
#    第3个和第5个字节 next_record 002B 下一行的记录为当前位置 + 002B
#        C0AC + 002B = C0D7 下一行数据的next_record位置# 第三行数据
03 01                         # 边长字段列表 逆序
06                            # null 标识位
00 00 20 00 1F                # record header
00 00 00 00 02 03             # rowid             
00 00 00 00 05 58             # transactionid
d3 00 00 01 52 01 10          # roll pointer
64                            # 第一列数据
66 66 66                      # 第四列数据
# 第一行 变长字段列表为逆序状态,转换回来为01  03,
#     对应第1列、第4列长度分别为1字节、3字节# 第二行 null 标识位 06 转换为二进制 0110
#    第二列、第三列 为 null# 第三行为 record header 占用5字节
#    第1个字节转换为二进制 0 0 0 0 0 0 0 0
#        未知 0 
#        未知 0
#        deleted_flag 0 改行未删除
#        min_rec_flag 0 该行不是最小记录
#        n_owned      0000  该组拥有的记录数 不记录在当前节点
#    第2个和第3个字节转为二进制 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0
#        heap_no 0 0 0 0 0 0 0 0 0 0 1 0 0 排在第 3 行数据
#        record_type 0 0 0 表示普通记录
#    第3个和第5个字节 next_record 001F 下一行的记录为当前位置 + 001F
#        C0D7 + 001F = C0F6 下一行数据的next_record位置# 当我们在插入 5 条数据
mysql> insert into mytest values ('d', null, null, 'fff');
Query OK, 1 row affected (0.00 sec)mysql> insert into mytest values ('e', null, null, 'ggg');
Query OK, 1 row affected (0.00 sec)mysql> insert into mytest values ('h', null, null, 'lll');
Query OK, 1 row affected (0.00 sec)mysql> insert into mytest values ('m', null, null, 'nnn');
Query OK, 1 row affected (0.01 sec)mysql> insert into mytest values ('o', null, null, 'ppp');
Query OK, 1 row affected (0.00 sec)# 解析一下第 4 条数据
03 01                         # 边长字段列表 逆序
06                            # null 标识位
04 00 28 00 1F                # record header
00 00 00 00 02 04             # rowid             
00 00 00 00 05 59             # transactionid
d4 00 00 01 53 01 10          # roll pointer
64                            # 第一列数据
66 66 66                      # 第四列数据
# 重点看一下 record header 第一个字节 04 其中 n_owned 占用4位等于4
#  表明当前数据为当前组的最后一条数据,其中这个组包含4条数据

四、验证 null 标识位

# 创建表结构
create table mytest1 (t1 varchar(10) not null
) engine=innodb charset=latin1 row_format = dynamic;# 插入数据
mysql> insert into mytest1 select 'aaa';
Query OK, 1 row affected (0.01 sec)# 用 notepad++ 打开 mytest1.ibd 文件,需要下载 hex-editor 插件, 定位到C078位置
03                            # 变长字段列表 逆序
00 00 10 ff f2                # record header 
00 00 00 00 04 0a             # rowid
00 00 00 00 0b 15             # transactionid
b2 00 00 01 26 01 10          # roll pointer
61 61 61                      # 第一列数据
# 整理完成后,我们发现, 当一行数据都是非空字段时,null 标识位是省略的

五、验证行溢出

# 创建表结构
mysql> create table mytest1 (->   t1 varchar(53353) not null-> ) engine=innodb charset=latin1 row_format = dynamic;
Query OK, 0 rows affected (0.02 sec)# 插入数据
mysql> insert into mytest1  select repeat('a', 53353);
Query OK, 1 row affected (0.01 sec)# 用 notepad++ 打开 mytest1.ibd 文件,需要下载 hex-editor 插件, 定位到C078位置
14 c0                # 变长字段标识
00 00 10 ff f1       # header 
00 00 00 00 04 0b
00 00 00 00 0b 1b
b6 00 00 01 2a 01 10# 使⽤ 20 个字节来标记这个溢出⻚的位置信息
00 00 00 31 
00 00 00 04 
00 00 00 26 
00 00 00 00 
00 00 d0 69 # 指针# 使用 py_innodb_page_info.py 查看 mytest1.ibd 文件
D:\ProgramData\MySQL\py_innodb_page_type>py_innodb_page_info.py -v "D:/ProgramData/MySQL/MySQL Server 5.7/MySQL_data/innodb_test/mytest1.ibd"
page offset 00000000, page type <File Space Header>
page offset 00000001, page type <Insert Buffer Bitmap>
page offset 00000002, page type <File Segment inode>
page offset 00000003, page type <B-tree Node>, page level <0000>
page offset 00000004, page type <Uncompressed BLOB Page>
page offset 00000005, page type <Uncompressed BLOB Page>
page offset 00000006, page type <Uncompressed BLOB Page>
page offset 00000007, page type <Uncompressed BLOB Page># 我们发现,存在一个数据页,存在 4 个溢出页,同时通过上面的验证,
# 我们知道数据行中存储的是 20 位的指针数据

六、实践 min_rec_flag 标识位

# 创建表结构 
create table mytest1 (t1 varchar(8090)
) engine=innodb charset=latin1 row_format = dynamic;# 插入数据
mysql> insert into mytest1  select repeat('a', 8090);
Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0mysql> insert into mytest1  select repeat('b', 8090);
Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0mysql> insert into mytest1  select repeat('c', 8090);
Query OK, 1 row affected (0.01 sec)
Records: 1  Duplicates: 0  Warnings: 0# 查看表空间情况
D:\ProgramData\MySQL\py_innodb_page_type>py_innodb_page_info.py -v "D:/ProgramData/MySQL/MySQL Server 5.7/MySQL_data/innodb_test/mytest1.ibd"
page offset 00000000, page type <File Space Header>
page offset 00000001, page type <Insert Buffer Bitmap>
page offset 00000002, page type <File Segment inode>
page offset 00000003, page type <B-tree Node>, page level <0001>
page offset 00000004, page type <B-tree Node>, page level <0000>
page offset 00000005, page type <B-tree Node>, page level <0000>
Total number of page: 6:
Insert Buffer Bitmap: 1
File Space Header: 1
B-tree Node: 3
File Segment inode: 1# 我们发现存在非叶子节点, 解析第一个非页子节点 
00             # 变长字段列表
10 00 11 00 10 # record header 
00 00 00 00 04 0c # 子目录最小值 主键
00 00 00 04 # 对应子目录位置# 对于第一行 00 表示为变长字段列表 对于非子节点 默认为 00# 第二行为 record header 占用 5 字节
#    第1个字节转换为二进制 0 0 0 1 0 0 0 0
#        预留字段 0 
#        预留字段 0
#        deleted_flag 0 该行 未删除
#        min_rec_flag 1 当前记录 为索引节点,同时为 最小记录,所以值为 1
#        n_owned      0000  该组拥有的记录数 不记录在当前节点
#    第 2 个和第 3 个字节转为二进制 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1
#        heap_no 0 0 0 0 0 0 0 0 0 0 0 1 0 排在第一行数据
#        record_type 0 0 1 表示索引数据
#    第3个和第5个字节 next_record 0010 下一行的记录为当前位置 + 0010
#        C07d + 0010 = C08d 下一行数据的 next_record 位置# 第三行 为:数据页最小主键,主键值为 00 00 00 00 04 0c
# 第四行 为:数据页编号,当前记录的叶子节点变化为:00 00 00 04
# 对应 page offset 00000004, page type <B-tree Node>, page level <0000>


http://www.ppmy.cn/news/1556187.html

相关文章

React 入门:JSX语法详解

简介 React是一个用于构建用户界面的JavaScript库&#xff0c;它引入了JSX语法&#xff0c;使得你可以在JavaScript代码中编写类似HTML的结构。JSX在编译后会被转换成合法的JavaScript对象。 JSX基础 JSX是一种看起来像HTML的JavaScript语法扩展。它并不直接被浏览器执行&am…

java程序语言设计-反射加设计模式

第六章:反射设计模式 一、反射 1. 反射(Reflection):允许在程序运行状态中&#xff0c;可以获取任意类中的属性和方法&#xff0c;并且可以操作任意对象内部的属 性和方法&#xff0c;这种动态获取类的信息及动态操作对象的属性和方法对应的机制称为反射机制。 2. 类对象 和 类…

Crawl4AI:一个为大型语言模型(LLM)和AI应用设计的网页爬虫和数据提取工具实战

这里写目录标题 一、crawl4AI功能及简介1、简介2、特性 二、项目地址三、环境安装四、大模型申请五、代码示例1.生成markdown2.结构化数据 一、crawl4AI功能及简介 1、简介 Crawl4AI 是一个开源的网页爬虫和数据抓取工具&#xff0c;一个python项目&#xff0c;主要为大型语言…

Android通过okhttp下载文件(本文案例 下载mp4到本地,并更新到相册)

使用步骤分为两步 第一步导入 okhttp3 依赖 第二步调用本文提供的 utils 第一步这里不做说明了&#xff0c;直接提供第二步复制即用 DownloadUtil 中 download 为下载文件 参数说明 这里主要看你把 destFileName 下载文件名称定义为什么后缀&#xff0c;比如我定义为 .mp4 下…

YOLO系列,从V1~V10,持续更新(Pytorch实现)

关于YOLOV1 本文主要偏向代码实战&#xff0c;详细的算法原理着墨不多 主要创新&#xff1a; 实现了实时的目标检测 附上YOLOV1论文地址&#xff1a; https://arxiv.org/abs/1506.02640 关于数据 首先要了解该网络架构的输入和输出&#xff0c;以及网络是如何进行训练的。这里…

workman服务端开发模式-应用开发-gateway的onWebSocketConnect开发

一、onWebSocketConnect安全开发一 为什么叫安全开发一&#xff0c;是因为还有两种情况没有考虑好&#xff0c;需要实测中进行修改。第一种情况&#xff1a;个人如果打开两个窗口&#xff0c;访问同一个系统或个人如果打开两种浏览器&#xff0c;两个窗口同时访问一个系统&…

gpu硬件架构

1.简介 NVIDIA在视觉计算和人工智能&#xff08;AI&#xff09;领域处于领先地位&#xff1b;其旗舰GPU已成为解决包括高性能计算和人工智能在内的各个领域复杂计算挑战所不可或缺的。虽然它们的规格经常被讨论&#xff0c;但很难掌握各种组件的清晰完整的图景。 这些GPU的高性…

多线程----互斥

以下是关于多线程中互斥相关的详细知识点介绍以及C语言代码示例&#xff1a; 一、互斥的概念与重要性 在多线程编程中&#xff0c;多个线程可能会同时访问和操作共享资源&#xff08;如全局变量、共享的数据结构等&#xff09;。如果没有适当的控制机制&#xff0c;就可能导致…