本文转载自InnoDB 的记录结构和页结构
InnoDB将数据划分为若干个页,以页作为磁盘和内存之间交互的基本单位,中页的大小一般为16KB。也就是在一般情况下,一次最少从磁盘中读取16KB的内容到内存中,一次最少把内存中的16KB内容刷新到磁盘中。
MySQL 里共有四种行格式:
CompactRedundantDynamicCompressed指定行格式的方法
CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名称
ALTER TABLE 表名 ROW_FORMAT=行格式名称

记录的额外信息分为3类:分别是变长字段长度列表、NULL值列表和记录头信息
变长字段长度列表是用来存储VARCHAR、TEXT等存储的字节数不确定的数据的长度。各变长字段数据占用的字节数按照列的顺序逆序存放,是逆序存放!
至于记录长度时用 1 个字节还是用 2 个字节,InnoDB 有一套规则。
用 1 个字节还是用 2 个字节的规则是:
if M × W <= 255 {
用一个字节
} else {
if L <= 127 {
用一个字节
} else {
用两个字节
}
}
总结一下:如果该可变字段允许存储的最大字节数超过 255 字节并且真实存储的字节数超过 127 字节,使用 1 个字节,否则使用 2 个字节。
另外,长度表只保存非NULL的长度,值为NULL的长度是不存储的。
用一位标记一个字段是否为 NULL,是 1 代表 NULL,0 代表 非BULL,逆序存放。整体以字节为最小单位,不足的高位补0。


delete_mask:删除标记min_rec_mask:B+树的每层非叶子节点中的最小记录都会添加该标记n_owned:当前记录拥有的记录数heap_no:当前记录在记录堆的位置信息record_type:当前记录的类型,0表示普通记录,1表示B+树非叶子节点记录,2表示最小记录,3表示最大记录。(记录的大小就是主键的大小)next_record:下一条记录的相对位置除了真实的列数据以外,还有隐藏列。
row_id:行唯一标识ID(非必须)transaction_id:事物IDroll_pointer:回滚指针真正列名称其实是:DBROWID、DBTRXID、DBROLLPTR
由于字段长度列表存的是边长字段长度列表,所以当修改字段类型时这个列表的长度也会发生变化。以及在修改字符集时,也会导致长度列表的长度发生变化。由此会导致重新分配空间,在原有的存储空间产生碎片化。
Redundant行格式是MySQL 5.0之前用的一种行格式,已经非常陈旧了,看看就好。

Compact格式记录的是长度可变的字段的长度,而Redundant记录的是所有字段的偏移量,一样是逆序存放。相邻两个字段偏移量的差就是该字段的长度。
delete_mask:删除标记min_rec_mask:B+树的每层非叶子节点中的最小记录都会添加该标记n_owned:当前记录拥有的记录数heap_no:当前记录在页面堆的位置信息n_field:当前记录中列的数量1byte_offs_flag:标记字段长度偏移列表中的偏移量是使用1字节还是2字节表示的next_record:表示下一条记录的相对位置和Compact相比有两处不同:
n_field和1byte_offs_flag。record_type。由于记录中没有 NULL 值表,记录 NULL 值的策略如下:
除了以上几点,和Compact的格式大致还是相同的。
Redundant格式没有Compact格式的碎片化问题。
一条记录除BLOB和TEXT类型之外的列(不包括隐藏列和记录头信息),占用的字节数长度之和不能超过65535字节。这65535字节包含:
在Compact和Reduntant格式中,真实记录数据的位置只会存储一部分数据,剩下的数据会存放在其他页中,并在真实记录数据的位置留下20字节的数据指向下一个页以及在下一个页的长度。

新的那些页称为溢出页。
MySQL规定每个页最少存放两条数据(原因后面再说),所以临界值是:
2个字节用于存储真实数据的长度1个字节用于存储列是否是NULL值5个字节大小的头信息6个字节的row_id列6个字节的transaction_id列7个字节的roll_pointer列136个字节所以只有一个列的表,行溢出的临界值是
136 + 2×(27 + n) > 16384
=> n > 8098
这个值本身没什么意义,只要有个概念就好了。
MySQL 5.7默认的行格式就是Dynamic,它和Compact仅仅是在行溢出时有区别。
在发生行溢出时,不会存放那768个字节,而是把所有的字节都放到其他页去,只记录地址。

逻辑和Dynamic一致,但增加了压缩算法,对页进行压缩,节省空间。
以上四种格式都会把过长的字段当做变长字段看待。
页是InnoDB的存储空间的基本单位,大小一般是16K。
InnoDB有许多不同的页:
以下内容针对索引(INDEX)页展开。

(图中的单位疑似错误,应为字节)
大致的结构为:
File Header:文件头部Page Header:页头部Infimum + Supremum:最小和最小两个虚拟行记录User Records:用户记录Free Space:空闲空间Page Directory:页面目录File Trailer:文件结尾当页刚被创建出来时,User Records是空的,每插入一条记录,就会从Free Space划过来一片空间存储,当Free Space用完时

记录和记录之间没有空隙,写在一起。

回头仔细看看记录的头信息:
delete_mask被标记为 1 代表这个记录已经被删除了,这些被删除的记录会组成一个垃圾链表,称为可重用空间。后续插入时会直接覆盖。 > 将这个delete_mask位设置为1和将被删除的记录加入到垃圾链表中其实是两个阶段,后面细说。min_rec_mask:每层非叶子节点中的最小记录都会添加该标记,后面细说。n_owned:当前记录拥有的记录数,稍后细说。heap_no:该记录在本页中的位置。InnoDB给表插入了两个虚拟记录,因此用户的记录是从 2 开始的。这两条记录没有被放在User Records部分,而是单独放在Infimum + Supremum部分。
record_type:0表示普通记录,1表示B+树非叶子节点记录,2表示最小记录,3表示最大记录。next_record:当前记录到下一条记录的真实数据开始的位置的偏移量,所以这玩意就是个链表。而链表的头尾就是最小记录和最大记录。当记录被删除后,类似链表删除,链表的指针会直接绕过这个节点。指针指到真实数据开始的位置,向左读取是头信息,向右读是真实数据,把变长字段长度列表和 NULL 值表放在最前面的原因也在此。这样读取更高效,无需解析各个字段的长度。
根据主键查询记录时,按照记录的链表顺序查肯定是低效的。InnoDB会制作该页的目录,制作的步骤:
n_owned属性表示该记录拥有多少条记录,也就是该组内共有几条记录。槽(Slot),所以页目录就是由槽构成的。
InnoDB规定,最小记录所在的组只能有1条记录,最大记录所在的组的记录数在1~8条之间,剩下的在4~8条之间。分组步骤:
n_owned值加1,表示本组内又添加了一条记录,直到该组中的记录数等于8个。8个后再插入一条记录时,会将组中的记录拆分成两个组,一个组中4条记录,另一个5条记录。这个过程会在页目录中新增一个槽来记录这个新增分组中最大的那条记录的偏移量。有了页目录,查询时只要使用二分法先找到这个主键所在的组,然后再遍历这个组的几条数据就可以了。
页头用来记录本页中存储了多少条记录,第一条记录在哪,页目录中存储了多少个槽等等。
PAGE_N_DIR_SLOTS:在页目录中的槽数量PAGE_HEAP_TOP:还未使用的空间最小地址,也就是说从该地址之后就是,后面就是Free SpacePAGE_N_HEAP:本页中的记录的数量(包括最小和最大记录以及标记为删除的记录)PAGE_FREE:第一个已经标记为删除的记录地址(各个已删除的记录通过next_record也会组成一个单链表,这个单链表中的记录可以被重新利用)PAGE_GARBAGE:已删除记录占用的字节数PAGE_LAST_INSERT:最后插入记录的位置PAGE_DIRECTION:记录插入的方向。假如新插入的一条记录的主键值比上一条记录的主键值比上一条记录大,我们说这条记录的插入方向是右边,反之则是左边。用来表示最后一条记录插入方向的状态就PAGE_N_DIRECTION:一个方向连续插入的记录数量。假设连续几次插入新记录的方向都是一致的,InnoDB会把沿着同一个方向插入记录的条数记下来,这个条数就用PAGE_N_DIRECTION这个状态表示。PAGE_N_RECS:该页中记录的数量(不包括最小和最大记录以及被标记为删除的记录)PAGE_MAX_TRX_ID:修改当前页的最大事务ID,该值仅在二级索引中定义PAGE_LEVEL:当前页在B+树中所处的层级PAGE_INDEX_ID:索引ID,表示当前页属于哪个索引PAGE_BTR_SEG_LEAF:B+树叶子段的头部信息,仅在B+树的Root页定义PAGE_BTR_SEG_TOP:B+树非叶子段的头部信息,仅在B+树的Root页定义(??的字段表示到目前为止需要知道的,剩下的后面再看)
FIL_PAGE_SPACE_OR_CHKSUM:页的校验和FIL_PAGE_OFFSET:页号。InnoDB里每个页单独一个页号,可以通过页号唯一确定一个页。FIL_PAGE_PREV:上一个页的页号FIL_PAGE_NEXT:下一个页的页号。各个页以双链表形式存储。并不是所有类型的页都有这两个属性,索引页是有的。
FIL_PAGE_LSN:页面被最后修改时对应的日志序列位置(Log Sequence Number)FIL_PAGE_TYPE:页的类型FIL_PAGE_FILE_FLUSH_LSN:代表文件至少被刷新到了对应的LSN值,仅在系统表空间的一个页中定义,FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID:页属于哪个表空间(??的字段表示到目前为止需要知道的,剩下的后面再看)
文件结尾用来校验数据完整性,共8个字节。
前4个字节代表页校验和,页面被修改时,校验和会被重新计算,写磁盘时头部的校验和会先被写进去,完全写完时尾部的校验和应与头部的相同,否则就代表写的过程中有错误。后4个字节代表页面被最后修改时对应的日志序列位置(LSN),这里暂时先不管是什么。原文:https://www.cnblogs.com/yungyu16/p/12952330.html