前言
大家好,我是 xicheng。当初持续更新 MySQL,本篇讲 InnoDB 的表空间,该部分类容比拟干燥繁琐,但又是 MySQL 后续内容的根底。所以大家能够先学习了解整体框架,等后续篇章用到的时候,再回过头查阅,进一步加深了解。另外,InnoDB 的常识脑图如下所示,大家坐稳了。
表空间
表空间(tablespace)由段(sagment)组成,段由区(extent)组成,区由页(page)组成,页由行组成。如下图所示。
所有数据都寄存在表空间中。如果用户手动启用了参数 innodb_file_per_table,则每张表的数据能够独自放在一个表空间中。
段
逻辑上的概念,⼀个索引会⽣成 2 个段,⼀个叶⼦节点段(寄存叶⼦节点的区),⼀个⾮叶⼦节点段(寄存⾮叶⼦节点的区)。
在刚开始向表中插⼊数据的时候,段是从某个碎⽚区(并不是所有页都是存储一个段的数据的区)以⻚为单位来调配存储空间的。
当某个段曾经占⽤了 32 个碎⽚区⻚⾯之后,就会以残缺的区为单位来调配存储空间(原先占用的碎片区的页不会被复制到新的区中来)。
常见的段由数据段,索引段,回滚段等。
区构造
对于 16KB 的页,物理地位间断的 64 个页就是一个区(extend),大小 1MB。256 个区被划分为 1 个组。
每个组中第一个区的固定页如下图所示。
第一组中第 0 区开始的 3 个⻚的类型是固定的:
- FSP_HDR(16KB):整个表空间的⼀些整体属性以及本组所有的区,整个表空间只有⼀个该类型的⻚⾯。
- IBUF_BITMAP(16KB):本组所有的区的所有⻚⾯对于 INSERT BUFFER 的信息。
- INODE(16KB):存储了许多 INODE 的数据结构。
其余各组最开始的 2 个⻚⾯的类型是固定的:
- XDES(extent descriptor):本组 256 个区的属性。
- IBUF_BITMAP:存储本组所有的区的所有⻚⾯对于 INSERT BUFFER 的信息。
- 在表中数据量⼤的时候,为某个索引调配空间的时候就不再依照⻚为单位调配了,⽽是依照区为单位调配。
区分类
闲暇的区:FREE,还没有⽤到这个区中的任何⻚⾯。
有残余空间的碎⽚区:FREE_FRAG,示意碎⽚区中还有可⽤的⻚⾯。
没有残余空间的碎⽚区:FULL_FRAG,示意碎⽚区中的所有⻚⾯都被使⽤,没有闲暇⻚⾯。
从属于某个段的区:FSEG。
区的 XDES Entry
构造
为了不便管理区而设计的。共 40 个字节,分为 4 个局部。
- SegmentID(8 字节):段惟一编号,示意就是该区所在的段(前提是该区已被调配给某段了,否则该字段无意义)。
- ListNode(12 字节):PreNodePageNumber(4 字节,前一页的页号)和 PreNodeOffset(2 字节,前一页的页号在页内的偏移量)指向前⼀个 XDESEntry。NextNodePageNumber(4 字节,后一页的页号)和 NextNodeOffset(2 字节,后一页的页号在页内的偏移量)指向后⼀个 XDESEntry。
- State:区的状态。参见“InnoDB 表空间 - 区分类”条目。
- PageStateBitmap:128 个⽐特位。每 2 个⽐特位对应区中的⼀个⻚。第⼀个位示意对应的⻚是否是闲暇的(1 闲暇。0 不闲暇),第⼆个⽐特位还没有⽤(1 没用。0 用了)。
XDES Entry 链表
通过 List Node 把 FREE 区对应的 XDES Entry 链接成一个链表,叫 FREE 链表。同一段中所有页面是闲暇的区的 XDES Entry 会被加到这个链表。
通过 List Node 把 FREE_FRAG 区对应的 XDES Entry 链接成一个链表,叫 FREE_FRAG 链表。同一段中还有闲暇页面区的 XDES Entry 会被加到这个链表。
通过 List Node 把 FULL_FRAG 区对应的 XDES Entry 链接成一个链表,叫 FREE_FRAG 链表。同一段中没有闲暇页面区的 XDES Entry 会被加到这个链表。
每个 XDES Entry 链表会有一个 List Base Node 节点,会被放在段的 INODE Entry。其中。
- ListLength:该链表总节点数。
- FirstNodePageNumber 和 FirstNodeOffset:该链表的头节点在表空间中的地位。
- LastNodePageNumber 和 LastNodeOffset:该链表的尾节点在表空间中的地位。
段的 INODE Entry
为了方便管理段而设计的。共 192 字节,被分为如下几个局部。
- Segment ID(8 字节):该 INODE Entry 对应的段号。
- NOT_FULL_N_USED(4 字节):NOT_FULL 链表的各 XDES Entry 节点对应的区曾经使⽤了多少⻚⾯。⼀个区中有 64 个⻚⾯,如果不标记曾经使⽤了多少⻚⾯的话,每次向段中插⼊数据的时候都要从第⼀个⻚⾯进⾏遍历寻找闲暇⻚⾯,有了这个字段之后就能够疾速定位闲暇⻚⾯。
- 3 个 List Base Node(别离都为 16 字节):别离为段的 FREE 链表、NOT_FULL 链表、FULL 链表定义了 ListBaseNode。
- Magic Number:标记这个 INODE Entry 是否曾经被初始化了(值是 97937874,表明曾经初始化,否则没有被初始化)。
- Fragment Array Entry:段是由零散的页面和残缺的区组成。每个 Fragment Array Entry 构造都对应着⼀个零散的⻚⾯,这个构造⼀共 4 个字节,示意⼀个零散⻚⾯的⻚号。
页类型
- FSP_HDR 类型
表空间的第一个页面,也是第一个组的第一个页面,页号为 0,存储表空间的整体属性及第一个组内内 256 区对应的 XDES Entry 构造。如下表所示。
名称 | 形容 | 占用空间(字节) | 作用 |
---|---|---|---|
File Header | ⽂件头部 | 38 | 页的通用信息 |
File Space Header | 表空间头部 | 112 | 表空间的⼀些整体属性信息 |
XDES Entry | 区形容信息 | 10240 | 存储本组 256 个区对应的属性信息 |
Empty Space | 尚未使⽤ 空间 | 5986 | ⻚构造的填充 |
File Trailer | ⽂件尾部 | 8 | 校验⻚是否残缺 |
File Space Header 如下表所示。
名称 | 占用空间(字节) | 形容 |
---|---|---|
Space ID | 4 | 表空间的 ID |
Not Used | 4 | 未使⽤ |
Size | 4 | 以后表空间占有的⻚⾯数 |
FREE Limit | 4 | 尚未被初始化的最⼩⻚号,⼤于或等于这个⻚号的区对应的 XDES Entry 构造都没有被加⼊ FREE 链表 |
Space Flags | 4 | 表空间的⼀些占⽤存储空间⽐较⼩的属性,不同 MySQL 版本有些差别 |
FRAG_N_USED | 4 | FREE_FRAG 链表中已使⽤的⻚⾯数量 |
3 个 List Base Node | 16/16/16 | FREE/FREE_FREG/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 链表的基节点 |
- XDES 类型
除了第一个分组的第一个页面是 FSP_HDR 类型之外,之后的每个分组的第⼀个⻚⾯只须要记录本组内所有的区对应的 XDES Entry 构造即可。就叫它 XDES 类型。如下图所示。
- IBUF_BITMAP 类型
每个分组的第⼆个⻚⾯的类型都是这种类型,这种类型的⻚⾥边记录了⼀些无关 Change Buffer 的信息。实质是一颗 B + 树。
在批改非惟一二级索引页面时,如果页面尚未被加载到内存中,那么该批改会被临时存储到 Change Buffer 中,等服务器闲暇或者对应页面从磁盘加载到内存时,再将批改合并到对应的页面。
- INDOE 类型
第⼀个分组的第三个⻚⾯。记录段的相干信息,如下表所示。
名称 | 形容 | 占用空间(字节) | 作用 |
---|---|---|---|
File Header | ⽂件头部 | 38 | 页的通用信息 |
List Node for INODE Page List | 通用链表节点 | 12 | 存储高低两个 INODE 页面的指针。如果一个表空间超过 85 个 INODE Entry,则须要额定的该类型页面来存储。 |
INODE Entry | 段形容信息 | 16320 | 具体的 INODE Entry 构造 |
Empty Space | 尚未使⽤空间 | 6 | ⻚构造的填充 |
File Trailer | ⽂件尾部 | 8 | 校验⻚是否残缺 |
INDOE 类型页面被划分为两个链表:
- SEG_INODES_FULL 链表:该链表中的 INODE 类型的⻚⾯中曾经没有闲暇空间来存储额定的 INODE Entry 构造了。
- SEG_INODES_FREE 链表:该链表中的 INODE 类型的⻚⾯中还有闲暇空间来存储额定的 INODE Entry 构造了。
新建段时,会创立 INODE Entry,存储 INODE Entry 的过程如下:
- 先看 SEG_INODES_FREE 链表是否为空,若不为空,间接从该链表中获取节点(页面),并把新的 INDODE Entry 放进去。当节点(页面)无空余空间时,就把该节点(页面)放到 SEG_INODES_FREE 中去。
- 若 SEG_INODES_FREE 为空,则须要从表空间的 FREE_FRAG 链表中申请一个页面,并将该页面的类型批改为 INODE,并退出 SEG_INODES_FREE 链表,而后把 INODE Entry 构造放入该页面。
Segement Header 构造
数据页的 Page Header 中有这两个字段:PAGE_BTR_SEG_LEA(10 字节,B+ 树叶⼦节点段的头部信息,仅在 B + 树的根⻚定义),PAGE_BTR_SEG_TOP(10 字节,B+ 树⾮叶⼦段的头部信息,仅在 B + 树的根⻚定义)。
这俩字段对应一个 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 Entry | 2 |
零碎表空间
独立表空间用于记录用户数据(上述内容都是讲的独立表空间),零碎表空间用于记录一些与整个零碎无关的信息。
零碎表空间表空间 ID(SpaceID)是 0。
第一个区的前三个页面与独立表空间是统一的,但第 4 个页面到第 8 个页面(页号从 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 | 数据字典头部信息,下文会讲到 |
后续区的前两个页面与独立表空间对应的区的页面是统一的。
元数据
更好地治理用户数据而引入的额定数据称为元数据。
记录元数据的零碎表如下表所示。用户不能间接拜访 InnoDB 的零碎表。
表名 | 作用 |
---|---|
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 存储引擎中所有的虚构⽣成列的信息 |
如下四个表尤为重要,且这四个表的元数据硬编码到代码中了。
- SYS_TABLES 表
(NAME 为主键,ID 列为二级索引)
表名 | 作用 |
---|---|
NAME | 表名 |
ID | InnoDB 存储引擎中每个表都有⼀个唯⼀的 ID |
N_COLS | 该表领有列的个数 |
TYPE | 表的类型,记录了⼀些⽂件格局、⾏格局、压缩等信息 |
MIX_ID | 疏忽 |
MIX_LEN | 疏忽 |
CLUSTER_ID | 疏忽 |
SPACE | 该表所属表空间的 ID |
- SYS_COLUMNS 表
(以 TABLE_ID,POS 为主键)
表名 | 作用 |
---|---|
TABLE_ID | 该列所属表对应的 ID |
POS | 该列在表中是第⼏列 |
NAME | 列的名称 |
MTYPE | 表的类型,记录了⼀些⽂件格局、⾏格局、压缩等信息 |
PRTYPE | 主数据类型,例如 INT、VARCHAR |
LEN | 该列最多占⽤存储空间的字节数 |
PREC | 疏忽 |
- SYS_INDEXES 表
(TABLE_ID,ID 为主键)
表名 | 作用 |
---|---|
TABLE_ID | 该索引所属表对应的 ID |
ID | InnoDB 存储引擎中每个索引都有⼀个唯⼀的 ID |
N_FIELDS | 该索引蕴含列的个数 |
TYPE | 索引的类型,⽐如聚簇索引、唯⼀索引 |
SPACE | 该索引根页面所在的表空间 ID |
PAGE_NO | 该索引根页面所在页号 |
MERGE_THRESHOLD | 如果⻚⾯中的记录被删除 MERGE_THRESHOLD,就把该⻚⾯和相邻⻚⾯合并 |
- SYS_FIELDS 表
(INDEX_ID,ID 为主键)
表名 | 作用 |
---|---|
INDEX_ID | 该列所属索引的 ID |
POS | 该列在索引中是第几列 |
COL_NAME | 列名 |
Data Dictionary Header 页面:用固定的⻚⾯来记录上述 4 个表的聚簇索引和⼆级索引对应的 B + 树地位,这个⻚⾯就是⻚号为 7 的⻚⾯。如下图与表所示。
名称 | 形容 | 占用空间(字节) | 作用 |
---|---|---|---|
File Header | ⽂件头部 | 38 | 页的通用信息 |
Data Dictionary Header | 数据字典头部 | 52 | 记录⼀些根本零碎表的根⻚⾯地位以及 InnoDB 存储引擎的⼀些全局信息 |
Unused | 未应用 | 4 | 未应用 |
Segment Header | 段头部 | 10 | 记录本⻚⾯所在段对应的 INODEEntry 地位信息 |
Empty Space | 尚未使⽤空间 | 6 | ⻚构造的填充 |
File Trailer | ⽂件尾部 | 8 | 校验⻚是否残缺 |
Data Dictionary Header 详解
- MaxRowID:不管哪个领有 row_id 列(InnoDB 暗藏列)的表插⼊⼀条记录时,该记录的 row_id 列的值就是 MaxRowID 对应的值,而后再把 MaxRowID 对应的值加 1,也就是说这个 MaxRowID 是全局共享的。
- MaxTableID:InnoDB 存储引擎中的所有的表都对应⼀个唯⼀的 ID,每次新建⼀个表时,就会把本字段的值作为该表的 ID,而后⾃增本字段的值。
- MaxIndexID:InnoDB 存储引擎中的所有的索引都对应⼀个唯⼀的 ID,每次新建⼀个索引时,就会把本字段的值作为该索引的 ID,而后⾃增本字段的值。
- MaxSpaceID:InnoDB 存储引擎中的所有的表空间都对应⼀个唯⼀的 ID,每次新建⼀个表空间时,就会把本字段的值作为该表空间的 ID,而后⾃增本字段的值。
- MixIDLow(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 表聚簇索引的根⻚⾯的⻚号。
结尾
InnoDB 的表空间就讲完了,心愿大家能继续学习。下一篇 MySQL 文章讲 InnoDB- 数据目录。
微信扫描下方二维码,或搜寻“xicheng”,关注公众号后回复【笔记】,有我筹备的 15 万字 Java 面试笔记。
感激各位人才的点赞、珍藏和评论,干货文章继续更新中,下篇文章再见!