MySQL记录行格式

70 阅读3分钟

前言:个人笔记记录!!! 个人笔记记录!!! 个人笔记记录!!!

1.环境搭建

MySQL:8.0.29,VS Code,Hex Editor插件

1.1 数据准备

创建完对应数据库后,获取对应.idb文件

CREATE TABLE `test_01` (
  `a` int NOT NULL AUTO_INCREMENT,
  `b` varchar(5) COLLATE utf8mb4_general_ci NOT NULL,
  `c` char(5) COLLATE utf8mb4_general_ci DEFAULT NULL,
  `d` varchar(5) COLLATE utf8mb4_general_ci DEFAULT NULL,
  PRIMARY KEY (`a`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
INSERT INTO `test_01` VALUES (1, '清和三十', '你', '他');
INSERT INTO `test_01` VALUES (2, '哈哈', NULL, NULL);

2. 理论分析

MySQL可以使用多种不同的格式保存记录:Compact,Redundant,Dynamic,Compressed;

2.1 Compact

先上格式图:

image.png

变长长度列表

存储的是变长字段占用的字节数(在变长字符集中,char类型的也会放入),逆序存放(指向记录中间,向左为记录额外信息,向右为真实数据)。目前只有bd列是变长字段,所以变长字段列表会保存这两列的占用字节数。如果某列占用的字节数比较多,该列需要用2个字节来表示。列最大字节数计算方式: 字符集最大字节x创建该列时指定的长度。通过上述计算出的字节数<=255,则使用1字节表示,就算大于255还是会判断实际占用字节数的,实际占用字节数<=127,使用1字节,否则2字节。

如果存在数据页溢出,则只存放本页的数据长度。该列表并不会记录值为NULL的列所占用的字节数

NULL值列表

如果表中没有允许存储 NULL 的列,则 NULL值列表 就为0。在本例中只有c,d列允许存储NULL值。每个NULL列占用一个二进制位,逆序存放。该处必须用整数个字节,不足高位补0

记录头信息

描述记录的额外信息,占用固定的5字节。这里就从别人那偷张图了😣

image.png

真实数据

在上面的格式图中,并没有画出隐藏列。除了我们自己添加的列外,MySQL还会为我们添加一些隐藏列:

列名占用字节描述
row_id6唯一标识一条记录,非必须
transaction_id6事务ID
roll_pointer7回滚指针

如果没有主键的话,就会表中UNIQUE列做为主键,这也没有,便会使用row_id

杂谈

在变长字符集中,CHAR(M)存储的范围至少是M个字节,防止后续更新需要重新分配空间。这对变长字段列表也是一样,就算只存了一个字符,在变长字段列表处用的长度还是M

3. 看见数据

上面理论分析完了,接下来就该实战了。VS Code 打开.idb文件,都是一堆16进制数据,可以验证下我们上面的理论。

忽略掉一些无用数据,先定位到变长字段列表处。根据上面的解析,得出第一条记录的变长字段列表应该由b,c,d这三列组成。b列4个字符,而字符集为utf8mb4,中文占3字节,得出b列占用12字节。 C列一个字符,但由于是可变字符集,同时还是char(M)类型,则实际字节数为5字节。 d列和b列计算方式一样,占用3字节。 这三个数都可以用1字节存储:12 5 3,逆序得3 5 12。如图

image.png 将这些长度转为16进制,正好是0x03,0x05,0x0C

接下来就是NULL值列表了,第一条记录没有NULL值,所以占一字节都是0就行了,如上图0c后面跟的就是1字节的NULL值列表

直接跳过5字节的记录头信息,来到真实数据处:

image.png

由于我们已经有了主键,row_id就没有存在的必要了。但是这里有个细节:虽然row_id没有存在的必要了,但是MySQL会将我们自定义的id放在row_id的位置,也就是说先往后数4字节就是自定义的主键id值,再往后数6字节就是transaction_id的值,再往后数7字节就是roll_pointer内容。后面的就是表中其他列的数据了。

ps:至于为什么主键值是0x80 00 00 01,而不是0x00 00 00 01,这是因为MySQL规定有符号数高位为1

0xE6开始就是第二列的数据了,也就是b列的值,utf8mb4中一个中文占3字节,4个字符也就是12字节,后面的列以此类推。虽然c列只有一个字符,但还是占用了5字节,后面补了两个0x20

第二条记录类似,这里就说下NULL值列表吧

image.png

0x06开始便是第二条记录,只有一列不为NULL的可变列,所以可变字段长度列表是0x06。由于我们有两列允许为NULL且为NULL(b,c列),就有两个标志位为1,再逆序存放就是0000 0011,转16进制就是0x03。后面的和第一条记录都是一样的逻辑啦。

4. 其他格式

Redundant属于比较老的一种了,它是用字段长度偏移量来计算的,了解即可。Dynamic就是Compact溢出列的情况下,不保存部分数据,而是将所有数据存放在其他页面中。

5. 闲聊

MySQL中一条记录最多65535个字节(不包括隐藏列和记录头信息),一条记录由真实数据,真实数据占用长度,NULL值标识(可以没有),除去2字节的数据长度列表(最坏的打算),NULL值标识2字节,那就只剩下65531字节了,假设使用utf8mb4,只有单列,则varchar(m)m最大只能在16382左右(前面两个最坏打算不精确),但让这只有一个列的情况下,还应该加上其他列

一个页16k,就是16384字节,一列最多65535字节,这会造成溢出,在compact中,记录的真实数据的地方会只保存一部分数据,然后用20字节存放溢出数据所在页的地址。发生行溢出公式:132+2x(27+n)<16384132为页中其他内容占用字节,27为记录中的额外信息,2MySQL规定一页中最少拥有2条记录,解出n<8099,这只是在只有一个列的情况下。