暂无图片
暂无图片
2
暂无图片
暂无图片
暂无图片

[MYSQL] innodb数据字段的物理存储结构

原创 大大刺猬 2025-08-27
237

导读

在整理innodb数据存储格式的时候, 发现有位大佬的博客 中说: 主键非叶子节点 不需要记录null bitmask.
image.png

按道理来讲, 确实是不需要(毕竟主键的非叶子节点只有主键值, 主键值一定是非空, 那就没必要记录null bitmask了), 但我们应该以实际为主(设计的时候可能没有考虑这些).

我们实际测试出来是有记录null bitmask的. 测试版本范围5.0 --> 8.0,主键非叶子节点均存在null bitmask.

测试验证过程

Mysql安装过程就不写了, 需要注意的是要在配置文件加上innodb_file_per_table=1, 老版本的该参数默认是0

这里得写1, 而不能写为on

可使用如下脚本快速生成测试数据:

DDL = "drop table if exists db1.t20250826_ddl;create table db1.t20250826_ddl(aa varchar(20) ,bb varchar(20),cc varchar(20), dd varchar(20),ee varchar(20), ff varchar(20), gg varchar(20), hh varchar(20),ii varchar(20), primary key(aa(19))) engine=innodb;insert into db1.t20250826_ddl values('test r0',1,2,3,4,5,6,7,8);" print(DDL) for i in range(10000): print(f"insert into db1.t20250826_ddl values('tt{i}',{','.join([ str(x) for x in range(8)]) });")

然后我们可使用如下脚本快速解析数据文件, 5.x版本没有sdi, 所以主键的root page是在pageid=3的位置.

f = open('/data/mysql5096/data/data/db1/t20250826_ddl.ibd','rb') f.seek(16384*3,0) data = f.read(16384) data[64:66] import struct offset = struct.unpack('>h',data[97:99])[0]+99 data[offset-7:offset] data[offset:][:20]

image.png

物理结构

于是呢, 我们就稍微整理了下innodb的数据结构.
image.png

主键里面记录了完整的字段信息的, 所以得单独考虑(而且还有instant算法在里面…)

主键 非叶子节点

pk_non_leaf.png

主键 叶子节点

pk_leaf.png

普通索引 非叶子节点

key_non_leaf.png

普通索引 叶子节点

key_leaf.png

名词解释

上面出现了一堆的名称, 我们来一一解释.

varsize: 对于变长字段(varchar,text,部分char)需要使用1-2字节来记录数据的实际存储长度. 若字段最大字节超过255, 则使用1-2字节(看第一字节是否达到128); 若字段最大字节不超过255, 则使用1字节存储即可.

null bitmask: 有些字段是没有非空约束的, 即可为空, 那么就需要使用1bit来记录其是否为空. 若为空,则varsize部分也不需要记录该字段了.

row_count: 如果是8.0.28及其之前的版本使用Instant算法增加字段时, 则之后新插入的数据行需要记录数据行数, 这样才知道这行数据的时候有多少个字段,不然使用instant多加几个字段就分不清这行数据有多少字段了. 使用1-2字节来记录, 和varsize一样的计算方式.

这里有个坑, 即instant增加字段时,不考虑字段长度是否达到限制, 也就导致有些表的行长度超过65535了…

row_version: 如果是8.0.28之后的版本使用instant加减字段时, 之后的数据行会使用1字节来记录row_version, 每次加减字段,则row_version值加1, 这样一看row_version,就知道这行数据加了多少字段,删了多少字段

record header: 一些字段头信息, 不同的row_format格式不一样. compact和dynamic格式如下:

对象 大小 描述
REC_INFO_INSTANT 01 bit 8.0.12-28是否使用add column instant
REC_INFO_VERSION 01 bit >=8.0.29 是否使用add/drop column instant
REC_INFO_DELETED 01 bit 这一行数据是否被删除了
REC_INFO_MIN_REC 01 bit 这一行数据是否是最小记录(non-leaf)
REC_N_OWNED 04 bit 这个slot有多少行数据(4-8)
REC_HEAP_NO 13 bit heap no
REC_STATUS 03 bit REC_TYPE, 字段类型
REC_NEXT 16 bit 下一个record的相对位置, 是有符号的.

redundant的格式如下:

对象 大小 描述
REC_INFO_INSTANT 01 bit 8.0.12-28是否使用add column instant
REC_INFO_VERSION 01 bit >=8.0.29 是否使用add/drop column instant
REC_INFO_DELETED 01 bit 这一行数据是否被删除了
REC_INFO_MIN_REC 01 bit 这一行数据是否是最小记录(non-leaf)
REC_N_OWNED 04 bit 这个slot有多少行数据(4-8)
REC_HEAP_NO 13 bit heap no
REC_N_FIELDS 10 bit 有多少个字段
REC_SHORT 01 bit 是否使用1字节存储长度
REC_NEXT 16 bit 下一个record位置

compressed比较特殊, 可参考之前写的compressed的格式的文章

primary key和key: 表示该索引的值, 如果是前缀索引,则只记录前缀部分.

child pageid: 记录的子叶的pageid, 子叶可能是叶子节点, 也可能是非叶子节点.

(pk) the rest of field: 记录剩下的字段信息, 如果是主键是前缀索引,这里还记录完整的主键值.

总结

mysql的叶子节点和非叶子节点, 主键索引和非主键索引均会记录null bitmask. 有的小伙伴可能会好奇, 主键的非叶子节点的字段都不为空啊, 那这个null bitmask是记录的谁的呢? 测试发现是记录的剩余字段的.但由于非叶子节点不存储剩余字段信息,所以这个null bitmask也就一直是0. (虽然一直是0,但也得记录).

我们只测试到了5.0版本, 并不能说更早之前的版本的存储格式.

参考:
https://blog.jcole.us/2013/01/10/the-physical-structure-of-records-in-innodb/

题外话

ibd2sql 2.x版本已经写好了(都TM写了1年了…),进入测试阶段了. 主要是优化了性能,扩展了支持范围,也支持解析多个文件,也支持并发.
image.png

web界面主要是加了个返回上页的功能
image.png
image.png

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论