前言
土人有土办法,利用innodb_ruby观测验证innodb page header及overflow临界点。
这是一篇封存了两年的文章,最近拿出来重新整理发布。
1、背景信息
1.1 InnoDB Diagrams项目
首先看一下Jeremy Cole、Davi Arnaut两位大神联合维护的牛逼项目:InnoDB Diagrams。
在这个项目中,详细介绍了InnoDB引擎数据结构、日志结构,以及InnoDB内部的运行机制。
为了配合这个项目,二位大神还开发了一个InnoDB数据文件解析工具 innodb_ruby
1.2 InnoDB Record Header
InnoDB Diagrams项目中有一张图介绍了InnoDB record header是如何构成的
关于这些头信息的解释可见文章 The physical structure of records in InnoDB,本文不赘述。
简言之,记住几条关键规则
- 一条记录的record header,至少是5字节
- 对record header影响最大的是变长列数量,及其是否允许为NULL的属性
关于变长列
- 每个变长列,如果列长度 <128 字节,则需要额外1字节
- 每个变长列,如果列长度 >=128 字节,则需要额外2字节
- 如果没有任何变长列,则无需这额外的1-2字节
- 变长类型为char/varchar/text/blob等
同学们可能会诧异,char为什么也当做变长类型了?这是因为,当字符集非latin1时,最大存储长度可能会超过255字节,例如 char(65) utf8mb4 最长就可以存储260字节,此时在record header中需要用2字节来表示其长度,因此也被当做变长类型了
关于列允许为NULL
- 每个列如果允许为NULL,则增加 1bit,不足8bit也需要额外1字节
- 例如只有2个列允许为NULL,只需要2bit来表示,但也需要占用1字节
P.S,在InnoDB的存储结构里,从tablespace到segment,再到extent、page,还是file层面,总有各种必要的header或trailer信息需要消耗额外的字节数,不像MyISAM那么简单。
1.3 innodb_ruby项目
上面提过,innodb_ruby工具可以帮助我们进一步理解InnoDB引擎数据结构、日志结构。
该项目用ruby语言开发(大神真是任性,选了这个比较冷门的开发语言)。
特别提醒,该项目已经多年未更新,有些数据类型发生了变化(例如最经典的5.6之后时间日期类型),它解析的可能就不准确了,在我下面的实测案例中也证实了这点。因此,我还用到另外一个辅助工具 innblock。
1.4 innblock工具
由八怪开发,用于扫描和分析InnoDB page,详见 innblock | InnoDB page观察利器
2、定义不同数据类型时的record header消耗
根据上面的理论,我们接下来计算并验证当表里有各种不同的数据类型时,分别需要多少额外字节。
关于测试环境
- MySQL版本:Percona Server 5.7.22-22
- 测试配套工具:innodb_ruby & innblock
特别提醒,测试表的字符集为utf8mb4。
经过计算和验证,最终可以得到以下几条结论:
- 每条记录的record header(下面简称RH)基础是5字节(简写成 RH=5)
- 每当表中多一个列允许为NULL,则额外增加1bit,且不足8bit时也需要消耗1字节(同理,不足16bit时需要消耗2字节)
- 每当表中多一个最大定义存储长度不超过255字节的变长列(char/varchar)时,额外增加1字节
- 每当表中多一个最大定义存储长度超过255字节的变长列(char/varchar/text/blob)时,额外增加2字节
由此我们可以推断出以下几种场景所需的record header大小(横屏观看,下同)
场景 | 表定义 | 行长度 (字节) |
record header (字节) |
备注说明 |
1 | id int not null 提醒:无显式主键 |
28 | 5 | 5(RH) +6(TRX_ID) +7(ROLL_PTR) +6(ROW_ID) +4(INT) = 28 |
2 | id int 提醒:无显式主键,且未指定not null |
29 | 6 | 6(允许null) + 6 +7 +6 +4 = 28 |
3 | id int not null primary key(id) 提醒:id列是显式主键 |
22 | 5 | 5 + 6 + 7 +4 = 22 |
4 | id int not null, c1 char(10), primary key(id) c1列只存储一个字符'a' |
34 | 7 | 7(char+null)+ 6 + 7 + 4 + 10(c1) = 34 |
5 | id int not null, c1 varchar(10), primary key(id) c1列只存储一个字符'a' |
25 | 7 | 7(varchar+null) + 6 + 7 + 4 + 1(c1) = 25 |
更多的场景,详见下面这个汇总表格