前言

大家好,我是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 ID4表空间的ID
Not Used4未使⽤
Size4以后表空间占有的⻚⾯数
FREE Limit4尚未被初始化的最⼩⻚号,⼤于或等于这个⻚号的区对应的XDES Entry构造都没有被加⼊FREE链表
Space Flags4表空间的⼀些占⽤存储空间⽐较⼩的属性,不同MySQL版本有些差别
FRAG_N_USED4FREE_FRAG链表中已使⽤的⻚⾯数量
3个List Base Node16/16/16FREE/FREE_FREG/FULL_FREG链表的基节点
Next Unused Segment ID8以后表空间中下⼀个未使⽤的Segment ID
List Base Node for SEG_INODES_FULL List16SEG_INODES_FULL链表的基节点
List Base Node for SEG_INODES_FREE List16SEG_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的过程如下:

  1. 先看SEG_INODES_FREE链表是否为空,若不为空,间接从该链表中获取节点(页面),并把新的INDODE Entry放进去。当节点(页面)无空余空间时,就把该节点(页面)放到SEG_INODES_FREE中去。
  2. 若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 Entry4INODE Entry构造所在的表空间ID
Page Number of the INODE Entry4INODE Entry构造所在的⻚⾯⻚号
Byte Offset of the INODE Entry2

零碎表空间

独立表空间用于记录用户数据(上述内容都是讲的独立表空间),零碎表空间用于记录一些与整个零碎无关的信息。
零碎表空间表空间ID(SpaceID)是0。

第一个区的前三个页面与独立表空间是统一的,但第4个页面到第8个页面(页号从3到7)是零碎表空间独有的,如下表所示。

页号页面类型英文名称作用
3SYSInsert Buffer Header存储Insert Buffer的头部信息
4INDEXInsert Buffer Root存储Insert Buffer的根⻚⾯
5TRX_SYSTransction System事务零碎的相干信息
6SYSFirst Rollback Segment第⼀个回滚段的⻚⾯
7SYSData 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表名
IDInnoDB存储引擎中每个表都有⼀个唯⼀的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
IDInnoDB存储引擎中每个索引都有⼀个唯⼀的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面试笔记。

  感激各位人才的点赞、珍藏和评论,干货文章继续更新中,下篇文章再见!