InnoDB表空间
前言
表空间是一个抽象的概念,对于系统表空间来说,对应着文件系统中一个或多个实际文件;对于每个独立表空间来说,对应着文件系统中一个名为表名.ibd 的实际文件。可以把表空间想象成被切分为许多个页的池子,当我们想为某个表插入一条记录的时候,就从池子中捞出一个对应的页来把数据写进去。
一、数据页
InnoDB是以页为单位管理存储空间的。前面学习过的B+数索引的页类型类型为索引页(也叫数据页),聚簇索引(也就是完整的表数据)和二级索引都是以B+树的形式保存到表空间的。页的通用结构为:
- File Header文件头:记录页面的一些通用信息。包括:
名称 | 占用空间(字节) | 描述 |
---|---|---|
FIL_PAGE_SPACE_OR_CHKSUM | 4 | 页的校验和(checksum值) |
FIL_PAGE_OFFSET | 4 | 页号 |
FIL_PAGE_PREV | 4 | 上一个页的页号 |
FIL_PAGE_NEXT | 4 | 下一个页的页号 |
FIL_PAGE_LSN | 8 | 页面被最后修改时对应的日志序列位置(英文名是:Log SequenceNumber) |
FIL_PAGE_TYPE | 2 | 该页的类型 |
FIL_PAGE_FILE_FLUSH_LSN | 8 | 仅在系统表空间的一个页中定义,代表文件至少被刷新到了对应的LSN值 |
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID | 4 | 页属于哪个表空间 |
FIL_PAGE_TYPE
属性表示页的类型,包括:
类型名称 | 16进制 | 描述 |
---|---|---|
FIL_PAGE_TYPE_ALLOCATED | 0X0000 | 最新分配,还未使用 |
FIL_PAGE_UNDO_LOG | 0X0002 | undo日志页 |
FIL_PAGE_INODE | 0X0003 | 存储段的信息 |
FIL_PAGE_IBUF_FREE_LIST | 0X0004 | Change Buffer空闲列表 |
FIL_PAGE_IBUF_BITMAP | 0X0005 | Change Buffer的一些属性 |
FIL_PAGE_TYPE_SYS | 0X0006 | 存储一些系统数据 |
FIL_PAGE_TYPE_TRX_SYS | 0X0007 | 事务系统数据 |
FIL_PAGE_TYPE_FSP_HDR | 0X0008 | 表空间头部信息 |
FIL_PAGE_TYPE_XDES | 0X0009 | 存储区的一些属性 |
FIL_PAGE_TYPE_BLOG | 0X000A | 溢出页 |
FIL_PAGE_INDEX | 0X45BF | 索引页,也称为数据页 |
- File Trailer 文件尾:校验页是否完整,保证从内存到磁盘刷新时内容的一致性。
二、独立表空间结构
InnoDB 支持许多种类型的表空间,这里重点关注独立表空间和系统表空间的结构。由于系统表空间中额外包含了一些关于整个系统的信息,所以先介绍简单一点的独立表空间。
2.1 区的概念
表空间中的页十分的多,为了更好的管理这些页面,引入了区(extent)的概念。对于16KB的页来说,连续的64个页就是一个区,也就是说一个区默认占用1MB空间大小。不论是系统表空间还是独立表空间,都可以看成是由若干个区组成的,每256个区被划分成一组。
上图中,左边不同的颜色代表不同的组,蓝色代表第一组,绿色第二组,依次后推。每个组有256个区,每个区由连续的64个页组成,大小为1MB。
右边表示这些组第一个区的头几个页面类型,可以看出:
-
第一个组最开始的3个页面的类型是固定的。每个属性表示含义:
- FSP_HDR 类型:这个类型的页面是用来登记整个表空间的一些整体属性以及本组所有的区的属性。需要注意的一点是,整个表空间只有一个FSP_HDR 类型的页面。
- IBUF_BITMAP 类型:这个类型的页面是存储本组所有的区的所有页面关于INSERT BUFFER 的信息。
- INODE 类型:这个类型的页面存储了许多称为INODE 的数据结构,用来记录段中的属性。
-
其余各组最开始的2个页面的类型是固定的,也就是说extent 256 、extent 512 这些区最开始的2个页面的类型是固定的。分别是:
- XDES 类型:全称是extent descriptor ,用来登记本组256个区的属性。上边介绍的FSP_HDR 类型的页面其实和XDES 类型的页面的作用类似,只不过FSP_HDR 类型的页面还会额外存储一些表空间的属性。
总结:表空间被划分为许多连续的区,每个区默认由64个页组成,每256个区划分为一组,每个组的最开始的几个页面类型是固定的。
2.2 段的概念
前面说到,一个区就是在物理位置上连续的64个页(区里页面的页号也是连续的)。在表中数据量大的时候,为某个索引分配空间的时候就不再按照页为单位分配了,而是按照区为单位分配,在表中的数据特别多的时候,甚至可以一次性分配多个连续的区。
以B+树索引举例,如果不区分叶子节点和非叶子节点,统统把节点代表的页面放到申请到的区中的话,进行范围查询时,需要对B+树叶子节点中的记录进行顺序扫描,这样效率非常低。针对这种情况,叶子节点有自己独有的区,非叶子节点也有自己独有的区。存放叶子节点的区的集合就算是一个段( segment ),存放非叶子节点的区的集合也算是一个段。也就是说一个索引会生成2个段,一个叶子节点段,一个非叶子节点段。
段是以区为单位申请存储空间的,一个区默认占用1M存储空间。分配后的区,即使段的数据填不满区中所有的页面,那余下的页面也不能被其他段使用。这样,一个区被整个分配给某一个段,对于数据量较小的表来说太浪费存储空间。
为了避免这种情况,提出了碎片区(fragment)的概念。在一个碎片区中,并不是所有的页都是为了存储同一个段的数据而存在的,而是碎片区中的页可以用于不同的目的,比如有些页用于段A,有些页用于段B,有些页甚至哪个段都不属于。碎片区直属于表空间,并不属于任何一个段。引入碎片区后,为某段分配存储空间的策略:
- 在刚开始向表中插入数据的时候,段是从某个碎片区以单个页面为单位来分配存储空间的。
- 当某个段已经占用了32个碎片区页面之后,就会以完整的区为单位来分配存储空间。
2.3 区的分类
状态名 | 含义 |
---|---|
FREE | 空闲的区,表示还没有用到这个区中的任何页面 |
FREE_FRAG | 有剩余空间的碎片区,表示碎片区中还有可用的页面 |
FULL_FRAG | 没有剩余空间的碎片区,表示碎片区所有页面已用完 |
FSEG | 附属于某个段的区 |
注意,处于FREE 、FREE_FRAG 以及FULL_FRAG 这三种状态的区都是独立的,算是直属于表空间;而处于FSEG 状态的区是附属于某个段的。
2.4 XDES Entry结构
XDES Entry(Extent Descriptor Entry)结构,用于管理区。每一个区都对应着一个XDES Entry 结构,这个结构记录了对应的区的一些属性。结构如下:
从图中可以看出, XDES Entry 是一个40个字节的结构,大致分为4个部分:
-
Segment ID (8字节):每一个段都有一个唯一的编号,用ID表示,此处的Segment ID 字段表示就是该区所在的段。
-
List Node (12字节):这个部分可以将若干个XDES Entry 结构串联成一个链表。结构如下:
可以看出,如果想定位表空间内的某一个位置的话,只需指定页号以及该位置在指定页号中的页内偏移量即可。
- State (4字节):这个字段表明区的状态。分别是: FREE 、FREE_FRAG 、FULL_FRAG和FSEG 。
- Page State Bitmap (16字节):这个部分共占用16个字节,也就是128个比特位。一个区默认有64个页,这128个比特位被划分为64个部分,每个部分2个比特位,按顺序对应区中的一个页。
2.4.1 XDES Entry链表
前面提出了一堆概念,只是为了减少随机IO,而又不至于让数据量少的表浪费空间。现在梳理一下,向某个段中插入数据时,申请新页面的过程:
当段中数据量较少的时候,首先会查看表空间中是否有状态为FREE_FRAG 的区,如果有,那么从该区中取一些零碎的页把数据插进去;否则到表空间下申请一个状态为FREE 的区,把该区的状态变为FREE_FRAG ,然后从该新申请的区中取一些零碎的页把数据插进去。之后不同的段使用零碎页的时候都会从该区中取,直到该区中没有空闲空间,然后该区的状态就变成了FULL_FRAG 。
问题一
现在的问题是你怎么知道表空间里的哪些区是FREE 的,哪些区的状态是FREE_FRAG 的,哪些区是FULL_FRAG 的?表空间的大小是可以不断增大的,当增长到GB级别的时候,区的数量也就上千了,总不能每次都遍历这些区对应的XDES Entry 结构吧?这时候就是XDES Entry 中的List Node 部分发挥奇效的时候了,可以通过List Node 中的指针,做这些事:
把每种状态的区对应的XDES Entry结构通过List Node连接成一个链表,分别称之为:FREE链表、FREE_FRAG 链表、FULL_FRAG 链表。
这样每当想找一个FREE_FRAG 状态的区时,就直接把FREE_FRAG 链表的头节点拿出来,从这个节点中取一些零碎的页来插入数据,当这个节点对应的区用完时,就修改一下这个节点的State 字段的值,然后从FREE_FRAG 链表中移到FULL_FRAG 链表中。同理,如果FREE_FRAG 链表中一个节点都没有,那么就直接从FREE 链表中取一个节点移动到FREE_FRAG 链表的状态,并修改该节点的STATE 字段值为FREE_FRAG ,然后从这个节点对应的区中获取零碎的页就好了。当段中数据已经占满了32个零散的页后,就直接申请完整的区来插入数据。
问题二
怎么知道哪些区属于哪个段的呢?因为不同的段不能共用一个区,所以想要每个段都有它独立的链表,可以根据段号(也就是Segment ID )来建立链表。有多少个段就建多少个链表?好像也有点问题,因为一个段中可以有好多个区,有的区是完全空闲的,有的区还有一些页面可以用,有的区已经没有空闲页面可以用了,所以有必要继续细分:
- FREE 链表:同一个段中,所有页面都是空闲的区对应的XDES Entry 结构会被加入到这个链表。注意和直属于表空间的FREE 链表区别开了,此处的FREE 链表是附属于某个段的。
- NOT_FULL 链表:同一个段中,仍有空闲空间的区对应的XDES Entry 结构会被加入到这个链表。
- FULL 链表:同一个段中,已经没有空闲空间的区对应的XDES Entry 结构会被加入到这个链表。
再次强调,每一个索引都对应两个段,每个段都会维护上述的3个链表。
2.4.2 链表基节点
前面介绍了许多链表,如何找到这些链表的头节点或者尾节点才是该了解的。为了定位头节点或尾节点,设计了一个名为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 结构放置在表空间中固定的位置,这样想找定位某个链表就变得十分容易。
2.4.3 链表小结
表空间是由若干个区组成的,每个区都对应一个XDES Entry 的结构,直属于表空间的区对应的XDES Entry 结构可以分成FREE 、FREE_FRAG 和FULL_FRAG 这3个链表;每个段可以拥有若干个区,每个段中的区对应的XDES Entry 结构可以分成FREE 、NOT_FULL 和FULL 这3个链表。每个链表都对应一个List Base Node 的结构,这个结构里记录了链表的头、尾节点的位置以及该链表中包含的节点数。正是因为这些链表的存在,管理这些区才变成容易。
2.5 段的结构
段不对应表空间中某一个连续的物理区域,而是一个逻辑上的概念,由若干个零散的页面以及一些完整的区组成。像每个区都有对应的XDES Entry 来记录这个区中的属性一样,每个段都定义了一个INODE Entry 结构来记录一下段中的属性。
- Segment ID:指这个INODE Entry 结构对应的段的编号(ID)。
- NOT_FULL_N_USED:这个字段指的是在NOT_FULL 链表中已经使用了多少个页面。
- 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个字节,表示一个零散页面的页号。
2.6 各类型页面详细情况
2.6.1 FSP_HDR类型
首先看第一个组的第一个页面,当然也是表空间的第一个页面,页号为0 。这个页面的类型是FSP_HDR ,它存储了表空间的一些整体属性以及第一个组内256个区的对应的XDES Entry 结构,直接看这个类型的页面的示意图:
从图中可以看出,一个完整的FSP_HDR 类型的页面大致由5个部分组成,各个部分的具体释义如下表:
名称 | 中文名 | 占用空间/字节 | 简单描述 |
---|---|---|---|
File Header | 文件头部 | 38 | 页的通用信息 |
File Space Header | 表空间头部 | 112 | 表空间的一些整体属性信息 |
XDES Entry | 区描述信息 | 10240 | 存储本组256个区对应的属性信息 |
Empty Space | 尚未使用空间 | 5986 | 用于页结构的填充 |
File Trailer | 文件尾部 | 8 | 校验页是否完整 |
a. File Space Header
各个属性的简单描述:
名称 | 占用空间/字节 | 描述 |
---|---|---|
Space ID | 4 | 表空间的ID |
Not Used | 4 | 这4个字节未被使用,可忽略 |
Size | 4 | 当前表空间占有的页面数 |
FREE Limit | 4 | 尚未被初始化的最小页号,大于或等于这个页号的区对应的XDES Entry结构都没有被加入FREE链表 |
Space Flags | 4 | 表空间的一些占用存储空间比较小的属性 |
FRAG_N_USED | 4 | FREE_FRAG链表中已使用的页面数量 |
List Base Node for FREE List | 16 | FREE链表的基节点 |
List Base Node for FREE_FRAG List | 16 | FREE_FREG链表的基节点 |
List Base Node for FULL_FRAG List | 16 | FULL_FREG链表的基节点 |
Next Unused Segment ID | 8 | 当前表空间中最大段的下一个未使用的 Segment ID |
List Base Node for SEG_INODES_FULL List | 16 | SEG_INODES_FULL链表的基节点 |
List Base Node for SEG_INODES_FREE List | 16 | SEG_INODES_FREE链表的基节点 |
b. XDES Entry
XDES Entry 就是在表空间的第一个页面中保存的。一个XDES Entry 结构的大小是40字节,但是一个页面的大小有限,只能存放有限个XDES Entry 结构,所以才把256个区划分成一组,在每组的第一个页面中存放256个XDES Entry 结构。
2.6.2 XDES 类型
每一个XDES Entry 结构对应表空间的一个区,虽然一个XDES Entry 结构只占用40字节,但在区的数量非常多时,一个单独的页可能就不够存放足够多的XDES Entry 结构,所以把表空间的区分为了若干个组,每组开头的一个页面记录着本组内所有的区对应的XDES Entry 结构。
与FSP_HDR相似,只是少了File Space Header这个用于记录表空间的属性。(无:表示没有使用)
2.6.3 IBUF_BITMAP 类型
2.6.4 INODE类型
第一个分组的第三个页面的类型是INODE。前面知道,每个索引定义了两个段,某些特殊功能也定义了些特殊的段。为了方便管理,每个段又设计了一个INODE Entry 结构,这个结构中记录了关于这个段的相关属性。这里要介绍的这个INODE 类型的页就是为了存储INODE Entry 结构而存在的。
从图中可以看出,一个INODE 类型的页面是由这几部分构成的:
名称 | 中文名 | 占用空间/字节 | 描述 |
---|---|---|---|
File Header | 文件头部 | 38 | 页的一些通用信息 |
List Node for INODE Page List | 通用链表节点 | 12 | 存储上一个INODE页面和下一个INODE页面的指针 |
INODE Entry | 段描述信息 | 16,320 | 具体的INODE Entry结构 |
Empty Space | 尚未使用空间 | 6 | 用于页结构的填充,没啥实际意义 |
File Trailer | 文件尾部 | 8 | 校验页是否完整 |
这里重点看一下List Node for INODE Page List:
因为一个表空间中可能存在超过85个段,所以可能一个INODE 类型的页面不足以存储所有的段对应的INODE Entry 结构,所以就需要额外的INODE 类型的页面来存储这些结构。还是为了方便管理这些INODE 类型的页面,需要将这些INODE 类型的页面串联成两个不同的链表:
- SEG_INODES_FULL 链表:该链表中的INODE 类型的页面中已经,没有空闲空间来存储额外的INODE Entry 结构。
- SEG_INODES_FREE 链表:该链表中的INODE 类型的页面中,还有空闲空间来存储额外的INODE Entry 结构。
前边提到过这两个链表的基节点就存储在File Space Header里边,也就是说这两个链表的基节点的位置是固定的,所以我们可以很轻松的访问到这两个链表。以后每当新创建一个段(创建索引时就会创建段)时,都会创建一个INODE Entry 结构与之对应,存储INODE Entry 的大致过程就是这样的:
- 先看看SEG_INODES_FREE 链表是否为空,如果不为空,直接从该链表中获取一个节点,也就相当于获取到一个仍有空闲空间的INODE 类型的页面,然后把该INODE Entry 结构防到该页面中。当该页面中无剩余空间时,就把该页放到SEG_INODES_FULL 链表中。
- 如果SEG_INODES_FREE 链表为空,则需要从表空间的FREE_FRAG 链表中申请一个页面,修改该页面的类型为INODE ,把该页面放到SEG_INODES_FREE 链表中,与此同时把该INODE Entry 结构放入该页面。
2.7 Segment Header结构的运用
我们知道一个索引会产生两个段,分别是叶子节点段和非叶子节点段,而每个段都会对应一个INODE Entry 结构,那怎么知道某个段对应哪个INODE Entry 结构呢?所以得找个地方记下来这个对应关系。在数据页里,也就是INDEX 类型的页时有一个Page Header 部分:
名称 | 占用空间/字节 | 描述 |
---|---|---|
… | … | … |
PAGE_BTR_SEG_LEAF | 10 | B+树非叶子段的头部信息,仅在B+树的根页定义 |
PAGE_BTR_SEG_TOP | 10 | 本页中的记录的数量(包括最小和最大记录以及标记为删除的记录),仅在B+树的根页定义 |
其中的PAGE_BTR_SEG_LEAF 和PAGE_BTR_SEG_TOP 都占用10个字节,它们其实对应一个叫Segment Header 的结构,该结构图示如下:
各个部分的具体释义如下:
名称 | 占用空间/字节 | 描述 |
---|---|---|
Space ID of the INODE Entry | 4 | INODE Entry结构所在的表空间ID |
Page Number of the INODE Entry | 4 | INODE Entry结构所在的页面页号 |
Byte Offset of the INODE Ent | 2 | INODE Entry结构在该页面中的偏移量 |
这样就比较清晰了,PAGE_BTR_SEG_LEAF 记录着叶子节点段对应的INODE Entry 结构的地址是哪个表空间的哪个页面的哪个偏移量, PAGE_BTR_SEG_TOP 记录着非叶子节点段对应的INODE Entry 结构的地址是哪个表空间的哪个页面的哪个偏移量。
三、系统表空间
系统表空间的结构和独立表空间基本类似,只不过由于整个MySQL进程只有一个系统表空间,在系统表空间中会额外记录一些有关整个系统信息的页面,所以会比独立表空间多出一些记录这些信息的页面。系统表空间相当于是表空间之首,它的表空间 ID (Space ID)是0 。
3.1 系统表空间的整体结构
系统表空间与独立表空间的一个非常明显的不同之处就是在表空间开头有许多记录整个系统属性的页面:
可以看到,系统表空间和独立表空间的前三个页面(页号分别为0 、1 、2 ,类型分别是FSP_HDR 、IBUF_BITMAP 、INODE )的类型是一致的,只是页号为3 ~ 7 的页面是系统表空间特有的。
页号 | 页面类型 | 英文描述 | 描述 |
---|---|---|---|
3 | SYS | Insert Buffer Header | 存储Insert Buffer的头部信息 |
4 | INDEX | Insert Buffer Root | 存储Insert Buffer的根页面 |
5 | TRX_SYS | Transction System | 事务系统的相关信息 |
6 | SYS | First Rollback Segment | 第一个回滚段的页面 |
7 | SYS | Data Dictionary Header | 数据字典头部信息 |
除了这几个记录系统属性的页面之外,系统表空间的extent 1 和extent 2 这两个区,也就是页号从64 ~ 191这128个页面被称为Doublewrite buffer ,也就是双写缓冲区。
3.2 InnoDB数据字典
平时使用INSERT 语句向表中插入的那些记录称之为用户数据,MySQL只是作为一个软件来为我们保管这些数据,提供方便的增删改查接口。MySQL除了保存着插入的用户数据之外,还需要保存许多额外的信息,比如:某个表属于哪个表空间,表里边有多少列等信息。这些信息不属于用户记录信息,是为了更好的管理这些用户数据而不得已引入的一些额外数据,这些数据也称为元数据。InnoDB存储引擎特意定义了一些列的内部系统表(internal system table)来记录这些这些元数据:
表名 | 描述 |
---|---|
SYS_TABLES | 整个InnoDB存储引擎中所有的表的信息 |
SYS_COLUMNS | 整个InnoDB存储引擎中所有的列的信息 |
SYS_INDEXES | 整个InnoDB存储引擎中所有的索引的信息 |
SYS_FIELDS | 整个InnoDB存储引擎中所有的索引对应的列的信息 |
SYS_FOREIGN | 整个InnoDB存储引擎中所有的外键的信息 |
SYS_FOREIGN_COLS | 整个InnoDB存储引擎中所有的外键对应列的信息 |
SYS_TABLESPACES | 整个InnoDB存储引擎中所有的表空间信息 |
SYS_DATAFILES | 整个InnoDB存储引擎中所有的表空间对应文件系统的文件路径信息 |
SYS_VIRTUAL | 整个InnoDB存储引擎中所有的虚拟生成列的信息 |
这些系统表也被称为数据字典,它们都是以B+ 树的形式保存在系统表空间的某些页面中,其中
SYS_TABLES 、SYS_COLUMNS 、SYS_INDEXES 、SYS_FIELDS 这四个表尤其重要,称之为基本系统表(basic
system tables)。
- SYS_TABLES表
列名 | 描述 |
---|---|
NAME | 表的名称 |
ID | InnoDB存储引擎中每个表都有一个唯一的ID |
N_COLS | 该表拥有列的个数 |
TYPE | 表的类型,记录了一些文件格式、行格式、压缩等信息 |
MIX_ID | 已过时,忽略 |
MIX_LEN | 表的一些额外的属性 |
CLUSTER_ID | 未使用,忽略 |
SPACE | 该表所属表空间的ID |
这个SYS_TABLES 表有两个索引:以NAME 列为主键的聚簇索引;以ID 列建立的二级索引。
- SYS_COLUMNS表
列名 | 描述 |
---|---|
TABLE_ID | 该列所属表对应的ID |
POS | 该列在表中是第几列 |
NAME | 该列的名称 |
MTYPE | 主数据类型(main data type),就是那堆INT、CHAR、VARCHAR、FLOAT、DOUBLE等 |
PRTYPE | 精准数据类型(precise type),就是修饰主要数据类型的那些,比如是否为NULL、自增等 |
LEN | 该列最多占用存储空间的字节数 |
PREC | 该列的精度,默认值为0 |
- SYS_INDEXES表
列名 | 描述 |
---|---|
TABLE_ID | 该列所属表对应的ID |
ID | InnoDB存储引擎中每个索引都有一个唯一的ID |
NAME | 该索引的名称 |
N_FIELDS | 该索引包含列的个数 |
TYPE | 该索引的类型,比如聚簇索引、唯一索引、更改缓冲区的索引、全文索引、普通的二级索引等等各种类型 |
SPACE | 该索引根页面所在的表空间ID |
PAGE_NO | 该索引根页面所在的页面号 |
MERGE_THRESHOLD | 如果页面中的记录被删除到某个比例,就尝试把该页面和相邻页面合并,这个值就是这个比例 |
这个SYS_INEXES 表只有一个聚集索引:以(TABLE_ID, ID) 列为主键的聚簇索引。
- SYS_FIELDS表
列名 | 描述 |
---|---|
INDEX_ID | 该索引列所属的索引的ID |
POS | 该索引列在某个索引中是第几列 |
COL_NAME | 该索引列的名称 |
这个SYS_INEXES 表只有一个聚集索引:以(INDEX_ID, POS) 列为主键的聚簇索引。
- Data Dictionary Header页面
只要有了上述4个基本系统表,也就意味着可以获取其他系统表以及用户定义的表的所有元数据。比方说我们想看看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个表的元数据,就是它们有哪些列、哪些索引等信息硬编码到代码中。然后,用一个固定的页面来记录这4个表的聚簇索引和二级索引对应的B+树位置,这个页面就是页号为7 的页面,类型为SYS ,记录了Data DictionaryHeader (也就是数据字典的头部信息)。页7的结构:
这个页包括:
名称 | 中文名 | 占用空间/字节 | 描述 |
---|---|---|---|
File Header | 文件头部 | 38 | 页的一些通用信息 |
Data Dictionary Header | 数据字典头部信息 | 56 | 记录一些基本系统表的根页面位置以及InnoDB存储引擎的一些全局信息 |
Segment Header | 段头部信息 | 10 | 记录本页面所在段对应的INODE Entry位置信息 |
Empty Space | 尚未使用空间 | 16272 | 用于页结构的填充,没啥实际意义 |
File Trailer | 文件尾部 | 8 | 校验页是否完整 |
可以看到这个页面里有Segment Header 部分,意味着这些有关数据字典的信息当成一个段来分配存储空间,称之为数据字典段吧
- information_schema系统数据库
需要注意一点的是,用户是不能直接访问InnoDB 的这些内部系统表的,除非你直接去解析系统表空间对应文件系统上的文件。但在系统数据库information_schema 中提供了一些以innodb_sys 开头的表,用来查看这些内部表的内容:
SHOW TABLES LIKE 'innodb_sys%';
在information_schema 数据库中的这些以INNODB_SYS 开头的表并不是真正的内部系统表(内部系统表就是上边唠叨的以SYS 开头的那些表),而是在存储引擎启动时读取这些以SYS 开头的系统表,然后填充到这些以INNODB_SYS 开头的表中。