欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 教育 > 幼教 > 【innodb阅读笔记】之 行格式(Dynamic)

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

2025/9/22 22:11:26 来源:https://blog.csdn.net/weixin_43262384/article/details/144499070  浏览:    关键词:【innodb阅读笔记】之 行格式(Dynamic)

一、背景

        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>

版权声明:

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

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

热搜词