页面类型
InnoDB是以页为单位管理存储空间的,我们的聚簇索引(也就是完整的表数据)和其他的二级索引都是以B+树的形式保存到表空间中的,而B+树的节点就是数据页。
这个数据页的类型名其实是:FIL_PAGE_INDEX
,除了这种存放索引数据的页面类型外,InnoDB也为了不同的目的设计了若干种不同类型的页面:
页面通用部分
数据页,也就是INDEX类型的页由七个部分组成,其中的两个部分是所有类型的页面都通用的。
可以看出,任何类型的页都包含以下两个部分:
File Header:记录页面的一些通用信息
File Trailer:校验页是否完整,保证从内存到磁盘刷新时内容的一致性。
File Header的组成部分:
- 表空间中的每一个页都对应一个页号,也就是FIL_PAGE_OFFSET,这个页号由4个字节组成,也就是32BIT。所以一个表空间最多拥有2的32次方个页。如果按照页的默认大小16KB来算,最多支持64TB的数据。第一个页的页号是0,后面的是1,2,3…
- 某些类型的页可以组成链表,链表中的页可以不安物理顺序存储,而是根据
FIL_PAGE_PREB
和FIL_PAGE_NEXT
来存储上一个页和下一个页的页号。需要注意的是,这两个字段主要是为了INDEX类型的页,也就是我们之前一直说的建立B+树后,为每层节点建立双向链表用的,一类型的页是不使用这两个字段的。 - 每个页的类型由FIL_PAGE_TYPE表示,比如像数据页的该字段的值就是0x45BF。
独立表空间结构
区的概念:
由于有时候表中的页面实在是太多了,为了更好的管理,提出了区的概念。对于16KB的页来说,连续的64个页就是一个区,也就是说一个区默认占1MB空间大小。不论是系统表空间还是独立表空间,都可以看成是由若干个区组成的,每256个区被划分为一组。
从上图出发我们不难获得以下信息:
第一个组开始的三个类型是固定的,也就是extent 0这个区最开始的三个页面是固定的:
- FSP_HDR类型:这个类型的页面被用来登记整个表空间的一些整体属性以及本组内所有的区,也就是extent 0 到extent 255 这256个区的属性。需要注意的是,整个表空间只有一个FSP_HDR类型的页面。
- IBUF_BITMAP类型:这个类型的页面是存储本组有所得区的所有页面关于INSERT BUFFER的信息。
- INODE类型:这个类型的页面存储了许多称为INODE的数据结构。
其余各组最开始的2个页面是固定的,也就是说extent256、extent512这些区最开始的两个页面类型是固定的
- XDES类型:全称是extent descriptor,用来等级本组256个区的属性,也就是说对于在extent256区中的该类型页面存储的就是extent256~extent 511这些区的属性,对于在extent 512区中的该类型页面存储的就是extent-512~extent 767这些区的属性。上面的FSP_HDR类型的页面其实和XDES类型的页面的作用类似,只不过FSP_HDR类型的页面还会额外存储一些表空间的属性。
- IBUF_BITMAP类型:同上
段的概念
考虑以下场景:
我们每向表中插入一条记录,本质上就是向该表的聚簇索引以及所有二级索引代表的B+树的节点中插入数据。而B+树的每一层中的页都会形成一个双向链表。如果是以页为单位来分配存储空间的话,双向链表相连的两个页之间的物理位置可能离得非常远。
在我们介绍B+树的时候,提高过只需要定位到最左边的记录和最右边的记录,然后沿着双向链表一直扫描就可以了。如果链表中相邻的两个页无论位置离得非常远。那么就是随机I/O,而且磁盘的速度和内存的速度差了好几个数量级,随机I/O是非常慢的,所以我们应该尽量让链表中相邻的页的物理位置也相邻,这样进行范围查询的时候才可以使用所谓的顺序I/O。
所以我们才引入了区的概念,在数据量非常大的时候,就可以直接以区为单位进行分配,甚至当数据非常多的时候,甚至可以一次分配几个区。
虽然会导致一点空间浪费(数据不一定能填满区),但是可以有效消除很多随机I/O,以空间换了时间。
我们再次回来考虑范围查询,其实是对B+树叶子节点中的记录进行顺序扫描,如果我们不区分叶子结点和非叶子结点,统统把结点放入申请到的区的话,效果当然就大打折扣了。所以自然的,我们应该把叶子结点和非叶子结点区别对待,也就是说他们各自应该由各自的区。存放叶子结点的区的集合就算是一个段(segment),存放非叶子结点的集合也是一个段,也就是说,一个索引会产生两个段,一个叶子结点段,一个非叶子结点段。
默认情况下一个使用InnoDB存储引擎的表只有一个聚簇索引,一个索引会生成2个段,而段是以区为单位申请存储空间的,一个区默认1M存储空间。
那么难道只存储了几个记录的一张小表也需要2M的存储空间吗?
当然不是,到目前为止,我们讨论的区都是十分纯粹的,即使段的数据填不满区中所有的页面,那余下的页面也不能被其他方面使用。
设计者提出了碎片(fragment)区的概念,也就是在一个碎片区内,并不是所有的页都是为了存储同一个段的数据而存在的,而碎片区中的页可以用于不同的目的,比如有些页用于段A,有些页用于段B,有些页甚至哪个段都不属于。
碎片区直属于表空间,并不属于任何一个段。
- 在刚开始向表中插入数据的时候,段是从某个碎片区以单个页面为单位来分配存储空间的。
- 当某个段已经占用了32个碎片区页面后,就会以完整的区为单位来分配存储空间。
所以现在段不能仅仅定义为是某些区的集合,更精确的应该是某些零散的页面以及一些完整的区的集合。
区的分类
我们知道了表空间是由若干个区组成的,这些区大体上可以分为4种类型:
- 空闲的区:现在还没有用到这个区中的任何页面
- 有剩余碎片的区:表示碎片区中还有可用的页面
- 没有剩余碎片的区:表示碎片区中所有的页面都被使用,没有空闲页面
- 附属于某个段的区。每一个索引都可以分为叶子节点和非叶子节点段,此外InnoDB还会定义一些特殊作用的段,在这些段中的数据量很大时将使用区来作为基本的分配单位。
这4种类型的区也可以被称为区的4种状态state
FREE、FREE_FRAG、FULL_FRAG这三种状态的区都是独立的,是直属于表空间,而FSEG状态的区是附属于某个段的。
为了方便管理这些区,设计者设计了一个XDES ENTRY的结构,每个区都对应着一个XDES Entry结构,这个结构记录了对应的区的一些属性:
由上图可以看出,XDES Entry是一个40字节的结构,大致分为4个部分,各个部分如下:
- Segment ID(8字节):每个段都有一个唯一的编号,用ID来表示,此处的Segment ID字段表示就是该区所在的段。当然前提是该区已经被分配给某个段了,不然的话该字段的值没什么意义。
- List Node(12字节)这个部分可以将若干个XDES Entry结构串联成一个链表,结构如下:
如果我们想定位表空间内的某一个位置的话,只需要指定页号以及该位置在指定页号内的页内偏移量即可。
因此 :
Prev Node Page Number 和Pre Node Offset的组合就是指向前一个XDES Entry的指针
Next Node Page Number 和 Next Node Offset的组合就是指向后一个XDES Entry的指针
- State(4字节):这个就是表明区的状态,就是上面4个。
- Page State Bitmap(16字节):这个部分占用16个字节,也就是128个bit位。我们说一个区默认有64个页,这128个比特位被划分为64个部分,每个部分占用2个比特位,对应区中的一个页。比如其中的第一个和第二个比特位对应着区中的一个页,第三个和第四个比特位对应着第二个页面,以此类推。这两个比特位的第一个位表示对应的页是否是空闲的,第二个比特位还没有用。
XDES Entry链表
我们做这些的目的仅仅是想提高表插入数据的效率又不至于数据量少的表浪费空间。
我们重新查看一个向某个段中插入数据的过程:
- 当段中数据较少的时候,首先会查看表空间中是否有状态为FREE_FRAG的区,也就是找还有空闲空间的碎片区,如果找到了 ,那么从该区中取一些零散的页把数据插进去;否则到表空间下申请一个状态为FREE的区,也就是空闲的区,把该区的状态变为FREE_FRAG,然后从该新申请的区中取一些零散的页把数据插进去。撞击后不同的段使用零散页的时候都会从该区中取,直到该区没有空闲空间,然后这个区的状态就变成了FULL_FRAG。
现在的问题变成了怎么才能知道哪些区是FREE的,哪些区的FREE_FRAG的,哪些区是FULL_FRAG的?
表的大小是可以不断增大的,当涨到GB级别的时候,区的数量也上千了,我们总不能每次都遍历这些区对应的XDES Entry结构吧?
这时候就轮到XDES Entry中的List Node部分出来做事了,通过这个指针,我们可以做以下三件事: - 把状态为FREE的区对应的XDES Entry结构通过List Node来连接成一个链表,这个链表我们就称之为FREE 链表。
- 把状态为FREE_FRAG的区对应的XDES Entry结构通过List Node来连接成一个链表,这个链表我们就称之为FREE_FRAG链表。
- 把状态为FULL_FRAG的区对应的XDES Entry结构通过ListNode来连接成一个链表,这个链表我们称之为FULL_FRAG链表。
每当我们想找一个FREE_FRAG状态的区时,就直接把FREE_FRAG的头结点拿出来,从这个节点中取一些零散的页来插入数据,当这个节点对应的区用完的时候,就修改一下这个节点的State字段的值,然后从FREE_FRAG链表中移到FULL_FRAG链表中。
同理,如果FREE_FRAG链表中一个节点都没有,那么就直接从FREE链表中取一个节点移动到FREE_FRAG链表的状态,并修改该节点的STATE字段值为FREE_FRAG,然后从这个节点对应的区中获取零散的页就好了。 - 当段中数据已经占满32个零散的页后,就直接申请完整的区来插入数据了。
那么问题又来了,应该如何确定哪些区属于哪个段呢?在遍历一次XDES Entry的结构?不可能呢,能用链表解决的问题我们一般都不会遍历。
可以尝试把FSEG的区对应的XDES Entry结构都加入到一个链表吗? 当然不行,不同的段肯定是不能共用一个区的。
很显然我们如果想要每个段都有它独立的链表,可以根据段号(Segment ID)来建立链表,那么有多少个段就建立多少个链表吗?好像也不太行,因为一个段中可以有好多个区,有的区是完全空闲的,有的区还有一些页面可以用,有的区已经没有空闲页面可以用了。
设计者为每个段中的区对应的XDES Entry结构建立了三个链表: - FREE链表:同一个段中,所有页面都是空闲的区对应的XDES Entry结构会被加入这个链表,注意和直属于表空间的FREE链表区分开,此处的FREE链表是附属于某个段的。
- NOT NULL链表:同一个段中,仍有空闲空间的区对应的XDES Entry结构会被加入这个链表
- FULL 链表:同一个段中,已经没有空闲空间的区对应的XDES Entry结构会被加入这个链表
再次强调,每一个索引都对应两个段,每个段都会维护以上的三个链表。
CREATE TABLE t (
c1 INT NOT NULL AUTO_INCREMENT,
c2 VARCHAR(100),
c3 VARCHAR(100),
PRIMARY KEY (c1),
KEY idx_c2 (c2)
)ENGINE=InnoDB;
这个表t共有两个索引,一个聚簇索引,一个二级索引idx_c2,所以这个表一共有4个段,每个段都会维护上述的3个链表,因此就一共是12个链表,再加上我们上面说过的直属于表空间的三个链表,整个独立表空间共需要维护15个链表,因此段在数据量比较大的时候插入数据时,会先获取NOT_FULL链表的头结点,直接把数据插入这个头结点对应的区中即可,如果该区的空间已经被用完,就把这个节点移到FULL链表中。
链表基节点
我们上面介绍了很多链表,那么这些链表如何被找到呢?或者说如何找到头结点、尾结点的位置呢?设计者设计了一个叫List Base Node的结构,翻译过来就是链表的基结点:
我们上面提到的每一个链表都对应这么一个List Base Node结构:
- List Length 表明该链表一共有多少个结点
- First Node Page Number 和 First Node Offset表明该链表头节点在表空间的位置
- Last Node Page Number 和 Last Node Offset表明该链表尾节点在表空间中的位置。
一般我们把某个链表对应的List Base Node结构放置在表空间中固定的位置,这样想找定位某个链表就变得很简单了。
段的结构
段其实不对应表空间中某一个连续的物理区域,而是一个逻辑上的概念,由若干个零散的页面以及一些完整的区组成。
像每个区都有对应的XDES Entry来记录这个区的属性一样,设计者为每个段都定义了一个INODE Entry结构来记录以下段中的属性:
各个部分:
- Segment ID: 就是指这个INODE Entry 结构对应的段的编号
- NOT_FULL_N_USED: 这个字段指的是在NOT_FULL链表中已经使用了多少个页面
- 3个List Base Node: 分别为FREE的链表、NOT_FULL链表、FULL链表定义了List Base Node,这样我们想查找某个段的某个链表的头节点和尾结点的时候,就可以直接找到这个部分找到对应链表的List Base Node!
- Magic Number:这个值是用来标记这个INODE Entry是否已经被初始化了,如果这个数字是97937874, 表明该INODE Entry以已经被初始化,否则没有被初始化。
- Fragment Array Entry:我们前边强调过无数次段是一些零散页面和一些完整的区的集合,每个Fragment Array Entry结构都对应着一个零散的页面,这个结构一共4个字节,表示一个零散页面的页号。
各类型页面详细情况
FSP_HDR类型
首先看第一个组的第一个页面,当然也是表空间的第一个页面,页号为0,这个页面的类型为FSP_HDR,它存储了表空间的一些整体属性以及第一个组内256个区的对应的XDES Entry结构,直接看这个类型的页面的示意图:
File Space Header部分
从名字就可以看出,这个部分是用来存储表空间的一些整体属性的:
FRAG_N_USED:这个字段表明在FREE_FRAG链表中已经使用的页面数量。
FREE_LIMIT:我们知道表空间都对应着具体的磁盘空间,一开始我们创建表空间的时候对应的磁盘空间都没有数据,所以我们需要对表空间完成一个初始化操作,包括为表空间中的区建立XDES Entry结构,为每个段建立INODE Entry结构,建立链表…的操作,我们一开始就可以为表空间申请一个特别大的空间,但是实际是有绝大部分的区是空闲的,我们可以选择把所有的这些空闲区对应的XDES Entry结构加入FREE链表,也可以选择**只把一部分的空闲区加入FREE链表,等什么时候空闲链表中的 XDES Entry结构不够用了,再把之前没有加入FREE链表的空闲区对应的XDES Entry结构加入FREE链表。 **简单来说,就是什么时候用得到,什么时候再去初始化,InnoDB用的就是后面这个方法。
也就是说,FREE Limit字段表示的页号之前的区都被初始化了,之后的区都还没有。
- Next Unused Segment ID:表中每个索引都对应两个段,每个段都有一个唯一的ID,当我们去为某个表新创建一个索引的时候,就意味着需要去创建两个新的段,那么如何为这个新的段找一个唯一的ID呢?Next Unused Segment ID就表示当前表空间中最大的段ID的下一个ID,所以我们如果想要创建新段,直接用这个字段的值作为ID就好啦。
- Space Flags:表空间对于一些BOOL类型的属性,或者只需要几个bit就能表示清楚的属性都放在Space Flags中存储,虽然只有4字节32位大小,但是能存储很多表的属性:
POST_ANTELOPE 1 表示文件格式是否大于ANTELOPE ZIP_SSIZE 4 表示压缩页面的大小
ATOMIC_BLOBS 1 表示是否自动把值非常长的字段放到BLOB页里 PAGE_SSIZE 4 页面大小
DATA_DIR 1 表示表空间是否是从默认的数据目录中获取的 SHARED 1 是否为共享表空间 TEMPORARY 1 是否为临时表空间
ENCRYPTION 1 表空间是否加密 UNUSED 18 没有使用到的比特位
XDES类型
每个XDES Entry结构对应表空间的一个区,虽然一个XDES Entry结构只占用40字节,但是表空间的区也非常多,在区的数量庞大的情况下,一个单独的页可能不足以存放足够的XDES Entry结构,所以我们把表空间的区划分为若干个组,每组开头的一个页面记录着本组内所有的区对应的XDES Entry结构。由于第一个区的第一个页面有些特殊,因为它也是整个表空间的第一个页面,除了记录本组内所有区对应的XDES Entry结构以外,还记录着表空间的一些整体属性,这个页面的类型就是我们上面提到的FSP_HDR类型,整个表空间里只有一个这个类型的页面。除去第一个分组以外,之后的每个分组的第一个页面只需要记录本组内所有的区对应的XDES Entry结构即可,不需要在记录表空间的属性了。为了和FSP_HDR类型做区别,我们把之后每个分组的第一个页面定义为XDES,它的结构和FSP_HDR类型非常相似:
与FSP_HDR的类型相比,除了File Space Header部分以外,也就是除了少了记录表空间整体属性的部分以外,其余的部分都是一样的。
IBUF_BITMAP类型
每个分组的第二个页面的类型都是IBUF_BITMAP,这种类型的页里面记录了一些有关Change Buffer的东西,这个Change Buffer里面概念又太多。
INODE 类型
第一个分组的第三个页面是INODe:
List Node for INODE Page List来说,因为一个表空间中可能存在超过85个段,所以可能一个INODE类型的页面不足以存储所有的段对应的INODE Entry结构,因此设计串联成两个不同的链表:
- SEG_INODES_FULL链表:该链表中的INODE类型的页面中已经没有空闲空间来存储额外的INODE Entry结构了
- SEG_INODES_FREE链表:该链表中的INODE类型的页面中还有空闲空间来存储额外的INODE Entry结构。
这两个链表的基结点就存储在File Space Header里面,也就是说,基节点的位置是固定的。
因此,创建新的段(创建索引就会创建段)的时候,都会创建一个INODE Entry结构与之对应,存储INODE Entry的大致过程如下: - 先看看SEG_INODE_FREE链表是否为空,如果不为空,直接从该链表中获取一个节点,也就相当于获取到一个仍有空闲空间 的INODE类型的页面,如何把该INODE Entry结构放到该页面中,如果该页面没有剩余空间了,那么就把该页放到SEG_INODES_FULL链表中。
- 如果SEG_INODES_FREE链表为空,则需要从表空间的FREE_FRAG链表申请一个页面,修改该页面的类型为INODE,把该页面放到SEG_INODES_FREE链表中,与此同时把INODE Entry结构放入该页面。
Segment Header 结构的运用
我们知道一个索引会产生两个段,一个叶子节点段一个非叶子节点段,而每个段都会对应一个INODE Entry结构,那么我们怎么知道哪个段对应哪个INODE Entry结构呢?当然是要找个地方来记下这个对应关系。INDEX类型的页有一个Page Header部分:
其中PAGE_BTR_SEG_LEAF和 PAGE_BTR_SEG_TOP都占用10个字节,其实他们都对应一个叫Segment Header的结构,图示如下:
PAGE_BTR_SEG_LEAF记录着叶子节点段对应的INODE Entry结构的地址是哪个表空间的哪个页面的哪个偏移量,PAGE_BTR_SEG_TOP记录着非叶子节点段对应的INODE Entry结构的地址是哪个表空间的哪个页面的哪个偏移量。这样子索引和其对应的段的关系就建立起来了。不过需要注意的一点是,因为一个索引只对应两个段,所以只需要在索引的根页面中记录这两个结构即可。
真实表空间对应的文件大小
.ibd文件是自动拓展的,随着表中数据的增多,表空间对应的文件也逐渐增大
系统表空间
系统表空间的结构和独立表空间基本类似,只不过由于整个MySQL进程只有一个系统表空间,在系统表空间中会额外记录一些有关整个系统信息的页面,所以会比独立表空间多出一些的记录这些信息的页面。因为这个表空间最厉害,相当于是表空间之首,所以它的表空间(Space ID)是0。
系统表空间的整体结构
系统表空间与独立表空间的一个非常明显的不同之处就是在表空间开头有许多记录整个系统属性的页面:
由上图可以看到,系统表空间和独立表空间的前三页页面(0,1,2)的类型是一致的,只是3~7的页面是系统表空间特有的,我们来看一下这些页面的作用:
除了这几个以外,系统表空间的extent1和extent2这两个区,也就是页号从64~191这128个页面被称为Doublewrite buffer,也就是双写缓冲区。
InnoDB数据字典
我们平时使用INSERT语句向表中插入的那些记录被称之为用户记录,MySQL只是作为一个软件来为我们报关这些数据,提供方便的增删改查结构而已。但是每当我们向一个表中插入一条记录的时候,MySQL要先校验一下插入语句对应的表存在不存在,插入的列和表中的列是否符合,如果没有语法问题的话,还需要知道该表的聚簇索引和所有二级索引的根页面是哪个表空间的哪个页面,然后把记录插入对应索引的B+树中。所以说,MySQL除了保存着我们插入的用户数据以外,还需要保存许多额外的信息:
- 某个表属于哪个表空间,表里面有多少列
- 表对应的每一个列的类型是什么
- 该表有多少索引,每个索引对应哪几个字段,该索引对应的根页面在哪个表空间的哪个页面
- 该表有哪些外键,外键对应哪个表的哪些列
- 某个表空间对应文件系统上的文件路径是什么
- …
上述的数据并不是我们INSERT语句所插入的用户数据,而是为了更好的管理用户数据而不得已引入的一些额外数据,这些数据我们也称之为元数据。InnoDB存储引擎特意顶一个一些列的内容系统表来记录这些元数据:
这些系统表也被称为数据字典,它们都是以B+树的形式保存在系统表空间的某些页面中,其中SYS_TABLES、SYS_COLUMNS、SYS_INDEXES、SYS_FIELDS这四个尤其重要,称之为基本系统表:
SYS_TABLES表
这个表有两个索引:
- 以NAME列为主键的聚簇索引
- 以ID列建立的二级索引
SYS COLUMNS表:
这个表只有一个聚簇索引:
以(TABLE_ID,POS)列为主键的聚簇索引
SYS_INDEXES表
这个SYS_INDEXES表只有一个聚簇索引:
- 以(TABLE_ID,ID)列为主键的聚簇索引。
SYS_FIELDES表
这个表只有一个聚簇索引:
- 以(INDEX_ID,POS)列为主键的聚簇索引
Data Dictionary Header页面
只要有了上述四个基本系统表,也就意味着可以获取其他系统表以及用户定义的表的所有元数据。比如说我们想看SYS_TABLESPACES这个系统表里存储了哪些表空间以及表空间对应的属性,那就可以:
-
到SYS_TABLES表中根据表名定位到具体的记录,就可以获取到SYS_TABLESPACES 表的TABLE _ID
-
使用这个TABLE_ID 到SYS_COLUMNS表中就可以获取到属于该表的所有列的信息。
-
使用这个TABLE_ID还可以到SYS_INDEXES表中获取所有的索引的信息,索引的信息中包括对应的INDEX_ID,还记录着该索引对应的B+数根页面是哪个表空间的哪个页面。
-
使用INDEX_ID就可以到SYS_FIELDS表中获取所有索引列的信息。
也就是说这4个表是表中之表,那这4个表的元数据去哪里获取呢?没办法了,只能把这4个表的元数据,就是它们有哪些列、哪些索引等信息硬编码到代码中,然后设计InnoDB的大叔又拿出一个固定的页面来记录这4个表聚簇索引和二级索引对应的B+树位置,这个页面就是页号为7的页面,类型为SYS,记录了Data Dictionary Header,也就是数据字典的头部信息。除了这4个表的5个索引的根页面信息外,这个页号为7的页面还记录了整个InnoDB存储引擎的一些全局属性:
可以看到这个页面由下边几个部分组成:
可以看到这个页面里居然有Segment Header 部分,意味着设计InnoDB的设计者把这些有关数据字典的信息当成一个段来分配存储空间,我们就姑且称为数据字典段吧。由于我们目前需要记录的数据字典信息非常少,所以该段只有一个碎片页,也就是页号为7的这个页。
Data Dictionary Header部分的各个字段:Max Row ID:我们说过如果我们不显式的为表定义主键,而且表中也没有UNIQUE索引,那么InnoDB存储引擎会默认为我们生成一个名为row_id的列作为主键。因为它是主键,所以每条记录的row_id列的值不能重复。原则上只要一个表中的row_id列不重复就可以了,也就是说表a和表b拥有一样的row_id列也没啥关系,不过设计InnoDB的大叔只提供了这个Max Row ID字段,不论哪个拥有row_id列的表插入一条记录时,该记录的row_id列的值就是Max Row ID对应的值,然后再把Max Row ID对应的值加1,也就是说这个Max Row ID是全局共享的。
Max Table ID:InnoDB存储引擎中的所有的表都对应一个唯一的ID,每次新建一个表时,就会把本字段的值作为该表的ID,然后自增本字段的值。
Max Index ID:InnoDB存储引擎中的所有的索引都对应一个唯一的ID,每次新建一个索引时,就会把本字段的值作为该索引的ID,然后自增本字段的值。
Max Space ID:InnoDB存储引擎中的所有的表空间都对应一个唯一的ID,每次新建一个表空间时,就会把本字段的值作为该表空间的ID,然后自增本字段的值。
Mix ID Low(Unused):这个字段没啥用,跳过。
Root of SYS_TABLES clust index:本字段代表SYS_TABLES表聚簇索引的根页面的页号。
Root of SYS_TABLE_IDS sec index:本字段代表SYS_TABLES表为ID列建立的二级索引的根页面的页号。
Root of SYS_COLUMNS clust index:本字段代表SYS_COLUMNS表聚簇索引的根页面的页号。
Root of SYS_INDEXES clust index本字段代表SYS_INDEXES表聚簇索引的根页面的页号。
Root of SYS_FIELDS clust index:本字段代表SYS_FIELDS表聚簇索引的根页面的页号。
Unused:这4个字节没用,跳过。