首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >[MYSQL] innodb数据字段的物理存储结构

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

原创
作者头像
大大刺猬
发布2025-08-27 18:27:25
发布2025-08-27 18:27:25
1780
举报
文章被收录于专栏:大大刺猬大大刺猬

导读

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

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

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

测试验证过程

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

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

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

代码语言:python
复制
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的位置.

代码语言:python
复制
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]

物理结构

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

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

主键 非叶子节点

主键 叶子节点

普通索引 非叶子节点

普通索引 叶子节点

名词解释

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

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年了...),进入测试阶段了. 主要是优化了性能,扩展了支持范围,也支持解析多个文件,也支持并发.

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 导读
  • 测试验证过程
  • 物理结构
    • 主键 非叶子节点
    • 主键 叶子节点
    • 普通索引 非叶子节点
    • 普通索引 叶子节点
    • 名词解释
  • 总结
  • 题外话
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档