提示:公众号展示代码会自动折行,建议横屏阅读

「前言」

InnoDB层的文件除日志文件外,都具有较为统一的物理结构。所有物理文件由页(page 或 block)构成,在未被压缩情况下,一个页的大小为UNIV_PAGE_SIZE(16384,16K)。不同用途的页具有相同格式的文件头和文件尾,其中记录了页面校验值、页面编号、表空间编号、LSN等通用信息。根据不同的应用场景和功能可以将页面分为多种类型,比如:每隔一定数量的页面后会使用extern描述页来记录每页空闲与否;Inode页面用于存储segment信息,segment是表空间管理的逻辑单位,每个索引占用2个segment,分别用于管理叶子节点和非叶子节点;索引页用于存储索引和用户记录;Blob页面用于记录溢出行的内容等等。InnoDB文件的结构可以详见《浅析InnoDB文件结构》。

本文主要讨论用户记录存储相关的数据页面(索引页和外部存储页)的物理结构以及组织方式。InnoDB用B+树的方式管理用户记录数据,每个索引对应一个B+树。B+树是通过索引页构建的,用户记录的数据存储在聚簇索引的叶子结点中。如果有变长字段(如text、blob、varchar)的长度过长,则可能会将该字段的全部数据或部分数据存储到外部存储页(blob页面)。

本文在第一部分、第二部分中详细介绍了索引页和外部存储页的组织方式,并以实际的数据文件进行举例说明。注意,前两部分以MySQL5.7为基础,介绍了非压缩页、dynamic类型的行格式的情况。在第三部分对压缩页和其他类型的行格式进行了补充说明。

「第一部分 索引页」

每个B+树通过两个segment来管理数据页,一个管理非叶子结点,一个管理叶子结点。这两个segment存储在B+树的root page中,在独立表空间中,聚簇索引的root page通常为第3个页面(从0开始计数)。索引页由七部分组成:文件头(Fil Header)、页头(Page Header)、最大最小记录(Infimum and Supremum Records)、用户记录(User Records)、空余空间(Free Space)、数据目录(Page Directory)、文件尾(Fil Trailer),其组成如下图所示:

为便于理解,本文引入了具体的例子来进行分析介绍,其建表语句以及数据操作的信息如下:

CREATE TABLE `t` (  `id` int(11) NOT NULL,  `c1` varchar(20) DEFAULT NULL,  `c2` varchar(60000) DEFAULT NULL,  PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO t(id, c1, c2) VALUES(1, 'a', '111aaa');INSERT INTO t(id, c1, c2) VALUES(2, 'b', '222bbb');INSERT INTO t(id, c1, c2) VALUES(3, 'c', '333ccc');
DELETE FROM t WHERE id = 2;

接下来将从上述七个部分对索引页进行详细的介绍。

1.1 文件头(Fil Header)

任何类型的页面都拥有38个字节的文件头,其主要用于checksum校验、页面类型判断、获取前后页面等,由以下8个部分组成:

补充说明:

1.FIL_PAGE_SPACE_OR_CHKSUM主要用来存储checksum,这里的checksum与文件尾的相对应,对于checksum的计算以及验证方式在文件尾部分进行详细的解释。

2.FIL_PAGE_LSN记录了最近修改的一次LSN。在崩溃恢复时,如果redo log的LSN小于当前的LSN,redo日志就不需要应用了。除此之外,该字段也应用于简单地判断页面是否损坏,页面最后4个字节的值与该字段后四位的值是一样的,并且如果该LSN大于系统目前最大的LSN号,也会认为页面是损坏的。

接下来通过hexdump查看上述例子中索引页的Fil Header。对于独立表空间而言,其索引页从第3页开始,且该页为B+树的root page。页面大小为16384,因此root page的Fil Header的偏移为 3 * 16384 = 49152 = 0xc000 。下面是该页面的Fil Header的实际内容:

$hexdump -s 0xc000 -n 38 -C t.ibd 0000c000  63 ad 74 69 00 00 00 03  ff ff ff ff ff ff ff ff  |c.ti............|0000c010  00 00 00 00 a8 cf e0 a4  45 bf 00 00 00 00 00 00  |........E.......|0000c020  00 00 00 00 00 55                                 |.....U|0000c026

其中 00 00 00 03 为页码,页码为3。因为在此例子中只有3条记录,B+树只使用了一个页面进行存储,所以其前一个和下一个页面均为 ff ff ff ff ,即FIL_NULL。页面类型的值为 45 bf ,为FIL_PAGE_INDEX,表示当前页面是索引页。

1.2 页头(Page Header)

这部分存储的是索引页的元信息,这部分由56个字节14个部分组成,其组成如下:

补充说明:

1.PAGE_N_DIR_SLOTS至少有两个,一个指向最小记录,一个指向最大记录,每个slot管理的记录为4~8个记录。关于page directory和slot的详细信息请看数据目录部分的介绍。

2.PAGE_LAST_INSERT、PAGE_DIRECTION、PAGE_N_DIRECTION主要用于加速插入操作。

3.PAGE_N_HEAP在创建空页的时候默认为2,即包含了最大记录、最小记录这两条系统记录。这个字段同时也用于判断记录的格式,如果第一个bit为0,则表示记录格式redundant类型。如果第一个bit为1,则记录格式为紧凑型的行格式(compact、dynamic或compressed格式,这三种方式的存储特性是一致的,唯一的区别在于行溢出时的处理,但是对未压缩的数据的解析流程是一致的)。

4.PAGE_BTR_SEG_LEAF和PAGE_BTR_SEG_TOP只在B+树的root page被设置,其他页面这两个字段的内容是未被使用的。这两个字段通过10个字节描述了对应的segment在inode page中的位置,其记录的内容如下:

接下来查看例子中root page的页头的实际存储内容,其在文件内的偏移0xc026为:root page的偏移(0xc000)+ Fil Header的长度38(0x26),内容如下:

$hexdump -s 0xc026 -n 56 -C t.ibd   0000c026  00 02 00 d8 80 05 00 a0  00 20 00 00 00 02 00 02  |......... ......|0000c036  00 02 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|0000c046  00 00 00 68 00 00 00 55  00 00 00 02 00 f2 00 00  |...h...U........|0000c056  00 55 00 00 00 02 00 32                           |.U.....2|0000c05e

部分内容解析说明:

1.0xc026~0xc028 -->00 02 :PAGE_N_DIR_SLOTS。从ibd的内容可以解析得到page directory中slot的数量为2,分别指向最大和最小记录。因为一个slot至少拥有4条记录,本例子中只有2条用户记录,所有只有两个默认的slot。

2.0xc028~0xc02a -->00 d8 :PAGE_N_HEAP。空余空间(Free Space)起始地址的页面偏移为 00 d8 ,由此可以计算剩余空间在文件内的偏移为 0xc000 + 0xd8 = 0xc0d8 ,查看0xc0d8地址的开始的内容全部为0。

$hexdump -s 0xc0d8 -n 100 -C t.ibd  0000c0d8  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|*0000c138  00 00 00 00                                       |....|0000c13c

3.0xc02a~0xc02c -->80 05 :PAGE_N_HEAP。第一个bit为1,表示记录为dynamic格式。剩余的bit计算的记录数为5。其由两条系统记录(Infimum和Supremum记录)、两条用户记录(id='1'和id='3')、以及一条删除的记录(id='2')组成。

4.0xc02c~0xc02e -->00 a0 :PAGE_FREE。第一条被删除的记录的相较于页面的偏移为 00 a0 ,查看其对应的记录如下。可以观察到其的确指向了被删除的记录,其中 80 00 00 02 为主键id的值,关于记录的组织方式会在后面进行详细的解释。

$hexdump -s 0xc0a0 -n 24 -C t.ibd  0000c0a0  80 00 00 02 00 00 00 00  61 3c 38 00 00 02 a0 05  |........a<8.....|0000c0b0  31 62 32 32 32 62 62 62                           |1b222bbb|0000c0b8

5.0xc036~0xc038 -->00 02:PAGE_N_RECS。值为 2 ,与id='1'和id='3'两条记录正确对应。

6.0xc040~0xc042 -->00 00:PAGE_LEVEL。值为 0 ,表示此页面为叶子页面,存储了用户的记录。

1.3 记录存储格式及解析

注意:本节例子中行的格式为dynamic类型,因此本节的内容主要基于dynamic类型的行格式。其他行格式补充在第三部分。Dynamic类型的行格式如下所示:

1.3.1 变长长度列表

对于varchar、text、blob等这类变长的字段,其存储长度是变长的。如果不记录该字段占用的真实长度,那么则无法正确地切分不同的字段,也无法正确的对字段进行解析。因此对于变长类型的字段不仅需要存储其真实的数据,还需要记录其真实长度。变长字段的长度便存储在变长字段长度列表中。

变长字段的存储以及解析遵循以下规则:

1.变长字段长度列表的存储是按照字段的逆序存放的,与真实数据的存放的顺序相反。

2.变长字段为空时,不会存在于变长字段长度列表中。

3.变长字段的最大长度小于255时,用一个字节记录其长度;最大长度大于255时,如果真实长度小于127,用一个字节表示,如果真实长度大于127时,用两个字节表示。比如对于一个VARCHAR(10000)的字段,如果其真实的长度为255字节,则会用两个字节记录其长度,如果真实长度为100字节,会使用一个字节存储该字段的长度。

4.对于最大长度大于255的记录,当在变长字段列表中对应的长度值的第一个字节的第一个比特位为0时,表示该长度用一个字节表示,为1则表示用两个字节表示。第二个比特位为1时,则表示该字段有行外数据存储在blob类型的页面上。对于行外存储的情况将在第二部分进行介绍。

注意:上述的长度为字节的长度而并非字符长度。比如在UTF-8编码中,一个中文字符占用的字节为3,如果一个变长字段存储了2个中文字符,其记录的长度为字节长度6,而并非2。

1.3.2 NULL值列表

为了节约空间,NULL值并不会占用存储空间。因此通过NULL值列表记录哪些字段为NULL,告知哪些字段不需要进行解析。

其主要遵循以下规则:

1.只有可以为NULL的字段才会在NULL值列表中。

2.NULL值列表通过BITMAP来标识每个字段是否为空,一个字段用一个比特位标识。如果字段为空,则为1,否则为0。

3.NULL值列表是按照字段顺序逆序排列的。

4.NULL列表占用的存储空间一定是8 bit的整数倍,如果可以为NULL的字段数不足8的倍数,在NULL值列表的高位补0。

1.3.3 记录头信息

记录头信息记录了记录的类型、下一条记录的地址等信息,固定头部信息占用5个字节,其组成如下:

1.3.4 最大最小记录解析示例

Infimum和Supremum记录为系统记录,没有变长字段列表和NULL值列表,只包含记录头信息和真实数据。其next_record分别指向页面中最小和最大的记录。它们的真实数据部分存储了字符串"infimum\0"和"supremum",其中在Dynamic格式中,infimum字符串后有一个0值,supremum记录没有。本节主要通过Infimum和Supremum记录的来了解记录头信息的解析。

$hexdump -s 0xc05e -n 26 -C t.ibd0000c05e  01 00 02 00 1d 69 6e 66  69 6d 75 6d 00 03 00 0b  |.....infimum....|0000c06e  00 00 73 75 70 72 65 6d  75 6d                    |..supremum|0000c078

部分内容解析说明:

1.n_owned字段解析:n_owned与前面四个部分共同占用一个字节,字节的后四位用来记录n_owned的值。其中Infimum的n_owned字段值为1,其slot只管理Infimum自身。Supremum记录的n_owned字段的值为3,其管理supremum记录、以及两条用户记录(id='3'和id='1')。

2.record_type字段解析:heap_no和record_type共占2个字节。以supremum为例,heap_no和record_type占用两个字节的值为 00b0 ,其每个比特的值为 0000000000001 011 ,record_type用后3个比特表示,record_type的值为3。同样可以计算得到Infimum记录的record_type为2。

3.next_record地段解析:Infimum的next_record指向的是第一条记录的真实数据的位置,next_record地址是相对于当前记录的真实数据地址的偏移,因此第一条记录的真实数据的地址为:0xc05e + 0x05 (记录头大小) + 0x001d (next_record的值) = 0xc080。

1.3.5 用户记录解析示例

从1.3.4的中解析得到第一条记录的真实数据的地址为0xc080,因为真实数据前有5个字节的记录头信息、1个字节的NULL值列表以及2个字节的变长字段列表,因此我们从地址0xc080-0x08=0xc078开始查看第一条记录的信息:

$hexdump -s 0xc078 -n 26 -C t.ibd0000c078  06 01 00 00 00 10 00 40  80 00 00 01 00 00 00 00  |.......@........|0000c088  61 39 b5 00 00 01 2a 01  10 61 31 31 31 61 61 61  |a9....*..a111aaa|0000c098

部分内容解析说明

1.变长字段长度列表:变长字段长度列表的值为 06 01 ,其按照字段的顺序逆序存储。字段c1的最大字符长度为20,其最大的字节长度小于255,因此其字段长度用一个字节 01 表示,其真实数据占用的字节数为1。字段c2的最大字节长度大于255,因此既有可能用一个字节记录长度也可能用两个字节记录其长度,而 06 的第一个比特位0,表示其用一个字节表示其长度,c2真实数据占用6个字节。

2.NULL值列表:该表只有两个字段c1和c2可以取空值,因此使用一个字节来记录空值列表。空值列表的值为 00 ,表示真实数据中没有字段取NULL。
3.记录头信息:接下来是记录头信息 00 00 10 00 40 ,记录头的信息在Infimum和Supremum记录头部分的解析解释过了,在此部分不再过多解释了。
4.真实数据部分解析:真实数据部分的第一部分为主键id。前四个字节 80 00 00 01 是主键的id,值为1。如果在没有指定主键的情况下,默认使用前6个字节记录MySQL自己生成的主键row_id。真实数据的第二部分为系统生成的隐藏列,用户不可见。主键后面6个字节 00 00 00 00 61 39 为事务ID,接下来7个字节为 b5 00 00 01 2a 01 10 为回滚指针。回滚段指针用于指向修改前的数据(数据的上一个版本),事务ID和回滚指针用于多版本并发控制(mvcc),当undo中的记录不再被用户需要时,会由后台的purge线程回收。隐藏列后的内容为用户自定义的字段,由变长字段列表可知c1的长度为1,故系统字段后的第一个字节 61 为c1的值,值为'a'。c2的长度为6, 31 31 31 61 61 61 为c2存储的内容,值为 111aaa 。

其解析的内容总结如下:

06 01                     变长字段列表,c1长度1,c2长度为200                        NULL值列表00 00 10 00 40            固定记录头信息80 00 00 01               主键,字段id的值,100 00 00 00 61 39         事务IDb5 00 00 01 2a 01  10     回滚指针61                        字段c1的值'a'31 31 31 61 61 61         字段c2的值'111aaa'

1.4 空余空间(Free Space)

这部分是介于用户记录和Page directory之间的一块连续的未被使用的内存。在空间足够时,会直接从这里分配内存,当空间不足时,会重新整理页面内的记录,将碎片空间进行合并。在将空间分配给记录后,会递增PAGE_N_RECS和PAGE_N_HEAP的值。

1.5 数据目录(Page Directory)

数据目录可以理解为页内的索引。其由一个个slot组成,每个slot由两个字节组成,每个slot指向一条记录,slot的值是记录在页面内的偏移。每个slot管理4~8条记录。在查找时首先数据目录上对slot进行二分查找,定位到具体的slot后,然后在slot内进行顺序查找。页内slot的组织方式如下:

注意:slot的顺序应当是逆序存储的,图中为了避免箭头交叉才顺序画的。

slot分配是从页面的最后倒数8个字节开始逆序分配的,一个page至少有两个slot,第一个slot指向Infimum记录,最后一个slot指向Supremum记录。

接下来看一下例子中数据目录的具体存储内容。在page header部分解析得到page directory中slot的个数为2。那么page directory的偏移为:0xfff4 = 0x10000(页面结束地址)- 0x08(页面末尾checksum和LSN后四位)- 0x04(一共两个slot四个字节)。数据目录的存储的内容如下:

$hexdump -s 0xfff4 -n 4 -C t.ibd0000fff4  00 70 00 63                                       |.p.c|0000fff8

因为页面的数据比较少,只有两个slot,分别指向Infimum和Supremum记录。下面进行简单的验证:

$hexdump -s 0xc063 -n 8 -C t.ibd0000c063  69 6e 66 69 6d 75 6d 00                           |infimum.|0000c06b
$hexdump -s 0xc070 -n 8 -C t.ibd0000c070  73 75 70 72 65 6d 75 6d                           |supremum|0000c078

其确实指向了Infimum和Supremum记录的真实数据部分。

1.6 文件尾(Fil Trailer)

文件尾的作用是校验文件是否损坏,在每个页面的结尾的8个字节中,分别存储了checksum和LSN的后四位,对应于FIL HEADER中的内容。下面是root page中文件尾的存储内容:

$hexdump -s 0xfff8 -n 8 -C t.ibd0000fff8  63 ad 74 69 a8 cf e0 a4                           |c.ti....|00010000

可以看到checksum的值为 63 ad 74 69 ,LSN后四位的值为 a8 cf e0 a4 ,与文件头内的内容是一致的。
MySQL中提供了如下6种页面checksum值计算的模式:

/** Possible values of the parameter innodb_checksum_algorithm */static const char* innodb_checksum_algorithm_names[] = {  "crc32",  "strict_crc32",  "innodb",  "strict_innodb",  "none",  "strict_none",  NullS};

上述六种校验模式实际上是三种算法。其中crc32和innodb都会计算页面的checksum,而对于none模式,这种方式并不会计算数据页的校验值,而是使用一个指定的值(BUF_NO_CHECKSUM_MAGIC)填充checksum字段。crc32和none模式下页面的文件头和文件尾的checksum是相同的,在innodb校验方式下,前后的checksum是不同的。这是因为在写checksum之前,文件尾的8个字节会先写8个字节的LSN号。在crc32和none模式中后面会覆盖文件尾的checksum字段。而在4.1版本之前,innodb校验方法并不会修改文件尾为checksum的sum字段,而新的版本中,innodb方法会将LSN号的前四个字节计算校验和,并覆盖文件尾的checksum字段。

在指定某种校验算法时,如果读取的页面时该算法校验失败,会继续尝试其他的算法进行校验。如果没有指定strict开头的算法,指定方式校验失败会尝试其余的2种校验方式,有一种通过校验方法通过就可以读取页面。如果指定了strict开头的算法,页面必须通过指定校验方法才可以读取。

「第二部分 外部存储页」

2.1 行溢出时索引页面记录的格式

每个页面至少应当存储两个记录,如果一个页面的大小为16k,那么一条记录最大不得超过8k,事实上应当更小,因为还有文件头、页头、文件尾等部分需要存储。如果变长的数据过长,导致索引页无法容纳两条记录,会将长度过长的字段的内容存储到外部存储页(blob page)。

在Dynamic格式中,过长字段的内容会全部存储到blob页面,索引页只存储20字节的指针指向外部存储页。其指针的内容如下所示:

在第一部分例子的基础上,执行以下SQL语句插入长字段。

insert into t(id, c1, c2) values(4,'4dd', REPEAT('d', 50000));

使用hexdump查看新插入记录的存储内容:

$hexdump -s 0xc0d8 -n 49 -C t.ibd0000c0d8  14 c0 03 00 00 00 28 00  31 80 00 00 04 00 00 00  |......(.1.......|0000c0e8  00 61 3e b9 00 00 01 2e  01 10 34 64 64 00 00 00  |.a>.......4dd...|0000c0f8  55 00 00 00 04 00 00 00  26 00 00 00 00 00 00 c3  |U.......&.......|0000c108  50                                                |P|0000c109

部分内容解析

1.c2字段长度解析:可以观察到,c2在变长字段长度列表中的值为c0,其对应的比特位为:11000000,第一个比特位为1,表示真实长度大于127,需要用两个字段表示字段的长度,第二个比特位为1,表示c2字段的值存储在外部blob字段中。剩余的比特位与第二个字节 14 记录了c2字段的长度,0x14的值为20,即只存储了20字节的指向外部存储页的指针。

2.解析指向外部存储页的指针:指针的内容为该记录的最后20个字节: 00 00 00 55 00 00 00 04 00 00 00 26 00 00 00 00 00 00 c3 50 。解析得到space id为0x55,外部存储页位于第4页,在blob 页面的偏移为0x26=38,表示外部存储页为非压缩页,总长度为0xc350=50000。

2.2 外部存储页结构

在非压缩页格式中,外部存储页由文件头、blob header、blob data组成。Blob header的组成如下:

非压缩的情况下,外部存储指针指向的页面偏移为38,指向blob header起始地址,如下所示:

如果外部存储页为压缩格式,其直接由文件头和压缩数据组成,外部存储页指针指向的页面偏移为12,指向FIL_PAGE_NEXT,如下所示:

接下来可以查看2.1中的例子中外部存储页的内容,在章节2.1中解析得到blob页面的页码为4,查看页码为四的全部内容:

$hexdump -s 0x10000 -n 16384 -C t.ibd00010000  23 1f 5c a2 00 00 00 04  00 00 00 00 00 00 00 00  |#.\.............|00010010  00 00 00 00 a8 d0 65 a6  00 0a 00 00 00 00 00 00  |......e.........|00010020  00 00 00 00 00 55 00 00  3f ca 00 00 00 05 64 64  |.....U..?.....dd|00010030  64 64 64 64 64 64 64 64  64 64 64 64 64 64 64 64  |dddddddddddddddd|*00013ff0  64 64 64 64 64 64 64 64  23 1f 5c a2 a8 d0 65 a6  |dddddddd#.\...e.|00014000

可以看到,除了文件头和文件尾以及blob header外,剩下的全部内容均为字段值,解析blob header的内容如下:

1.该页中存储的长度为 00 00 3f ca ,转为10进制为16330,等于16384(页面大小)- 38(文件头大小)- 8(文件尾大小) - 8 (blob header)。

2.下一个blob的页码为 00 00 00 05 ,页码为5。

「第三部分 补充内容」

3.1 其他类型行格式

InnoDB存储引擎支持四行格式:redundant、compact、dynamic和compressed。每种类型行格式的对比如下:

在章节1.3中详细的介绍了dynamic格式,也是目前5.7和8.0默认的行格式。除redundant格式,compact、compressed格式与dynamic格式差异很小,接下来对其他三种行格式进行概述。

Redundant行格式:

Redundant格式是为了与MySQL5.0之前的版本兼容,InnoDB文件格式(Antelope和Barracuda)都支持Redundant格式。通常并不会采用redundant格式,因为它是非紧凑类型的,比较占用磁盘空间,同样的页面中存储的记录行更少,索引的效率较低。其记录的格式如下:

Redundant行格式与dynamic格式的不同之处在于并没有区分定长和变长字段,而是将所有列占用的存储空间都逆序存储在字段长度偏移列表中。并且redundant格式并不存在null值列表,使用字段长度值的第1位来判断字段是否为空,如果第1位为1,则为空。因为第1位用来记录字段是否为NULL,所以一个字节所能表示的最大长度为127。

Redundant格式的记录头占用了6个字节,分为了9部分,相较于dynamic格式多了n_field和1byte_offs_flag字段,少了record_type字段,格式如下所示:

除上述不同之处外,redundant格式对于行溢出的处理也区别于dynamic格式,其会在索引页存储变长字段的前768字节的数据+外部存储页指针。

Compact类型行格式

Compact是一种紧凑类型的存储格式,与dynamic类型的存储格式基本一致,区别之处主要在于溢出行的处理方式。Compact类型在处理行溢出时,与redundant类型一样会在索引页存储变长字段的前768字节的数据+外部存储页指针。与redundant格式相比,compact行格式减少了约20%的行存储空间。

Compressed类型行格式

Compressed类型与dynamic类型拥有相同的存储特性和功能,不同之处在于使用压缩算法对页面进行压缩,包括溢出页。优点在于可以节约存储空间,但是在查找数据时需要先解压才行,会消耗更多的cpu资源。在建表时指定compressed格式,可以同时指定KEY_BLOCK_SIZE。KEY_BLOCK_SIZE会控制压缩后页面的大小,指定的大小必须小于当前默认数据页的大小。如果没有指定KEY_BLOCK_SIZE,则会自动设置为默认数据页大小的一半。要使通用表空间包含压缩表(ROW_FORMAT=COMPRESSED),必须指定FILE_BLOCK_SIZE选项,如果小于当前默认数据页的大小,会自动设置为compressed格式。其中FILE_BLOCK_SIZE的单位为byte,KEY_BLOCK_SIZE的单位为kb。
Compressed格式实际上是进行表压缩,具体可以参考3.2压缩表部分。

3.2 压缩页

目前索引页的压缩形式存在两种:一种是透明页压缩,一种是传统的压缩表格式。

透明页压缩

透明页压缩利用了Linux punch hole的新特性,对除文件头之外的地方进行压缩,在写入文件后进行打洞操作,读入文件后进行解压操作。其利用文件头部的FIL_PAGE_FILE_FLUSH_LSN字段的8个字节存储压缩信息,内容如下:

在Linux系统上,文件系统块大小是用于打孔的单元大小。因此,只有当页面数据可以压缩到小于或等于InnoDB页面大小减去文件系统块大小时,页面压缩才有效。例如,如果innodb_page_size=16K,并且文件系统块大小为4K,则页面数据必须压缩到小于或等于12K,才能进行打孔。透明页压缩的使用方式:

CREATE TABLE t1 (c1 INT) COMPRESSION="zlib";

压缩表

压缩表格式在进行更新或者插入操作时,数据会被保存在页面的mlog区域,当mlog快被填满时,页面才会重新进行压缩。因此页面中可能同时存在压缩数据和非压缩的数据。其页面的具体格式如下:

启用方式:

CREATE TABLE t1(c1 INT) ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;

「第四部分 总结」

本文详细阐述了InnoDB用于存储用户记录的相关页面的物理存储结构,并举例对每一部分的内容进行了手动解析。本文主要以非压缩页、dynamic类型的行数据格式进行了举例剖析,压缩页以及其他行格式的情况差异并不大,读者可以根据文章第三部分补充的内容尝试压缩页或其他类型行格式的解析。

「第五部分 参考文献」

https://dev.mysql.com/doc/internals/en/innodb-record-structure.html

https://dev.mysql.com/doc/internals/en/innodb-page-structure.html

https://dev.mysql.com/doc/refman/5.7/en/innodb-compression-background.html

https://dev.mysql.com/blog-archive/externally-stored-fields-in-innodb/

https://dev.mysql.com/blog-archive/innodb-transparent-page-compression/

https://dev.mysql.com/doc/refman/5.7/en/innodb-row-format.html

https://dev.mysql.com/doc/refman/5.7/en/general-tablespaces.html

腾讯数据库技术团队对内支持QQ空间、微信红包、腾讯广告、腾讯音乐、腾讯新闻等公司自研业务,对外在腾讯云上依托于histore的底座,支持TencentDB相关产品,如TDSQL-C(原CynosDB)、TencentDB for MySQL(CDB)等。腾讯数据库技术团队专注于持续优化数据库内核和架构能力,提升数据库性能和稳定性,为腾讯自研业务和腾讯云客户提供“省心、放心”的数据库服务。此公众号旨在和广大数据库技术爱好者一起推广和分享数据库领域专业知识,希望对大家有所帮助。

文章来源于腾讯云开发者社区,点击查看原文