头图

InnoDB-表空间

前言

大家好,我是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面试笔记。
图片
  感谢各位人才的点赞、收藏和评论,干货文章持续更新中,下篇文章再见!

一个互联网从业者,一起聊工作,聊生活。

1 声望
0 粉丝
0 条评论
推荐阅读
InnoDB-数据目录
以MySQL8.0为例,不同版本可能会有出入。通过SHOW VARIABLES LIKE 'datadir'查看数据目录。InnoDB和MyISAM这两种存储引擎,创建一个数据库时,会在数据目录下创建一个与数据库同名的文件夹。

西城阅读 653

封面图
一文搞懂秒杀系统,欢迎参与开源,提交PR,提高竞争力。早日上岸,升职加薪。
前言秒杀和高并发是面试的高频考点,也是我们做电商项目必知必会的场景。欢迎大家参与我们的开源项目,提交PR,提高竞争力。早日上岸,升职加薪。知识点详解秒杀系统架构图秒杀流程图秒杀系统设计这篇文章一万多...

王中阳Go34阅读 2.6k评论 1

封面图
SegmentFault 思否面试闯关挑战赛!
“金三银四”求职季到啦,能力自查与提升迫在眉睫🔥 社区专栏与技术问答联合推出: SegmentFault 思否面试闯关挑战赛 本期挑战赛于 3 月 7 日 正式开赛,共设四重关卡,不限技术方向。快来和小伙伴们一起主动追击,...

SegmentFault思否13阅读 10.9k评论 16

封面图
万字详解,吃透 MongoDB!
MongoDB 是一个基于 分布式文件存储 的开源 NoSQL 数据库系统,由 C++ 编写的。MongoDB 提供了 面向文档 的存储方式,操作起来比较简单和容易,支持“无模式”的数据建模,可以存储比较复杂的数据类型,是一款非常...

JavaGuide8阅读 1.7k

封面图
疫情已过,2023 我的前端面试记录
顺利入职。把我最近找工作的心得记录下来。工作交接确定 lastday整理手头工作,相关对接人、交接人放文档中工作交接过渡阶段。做好被咨询者,该拉人拉人,该拉群拉群平时沟通顺畅的同事如果没有 WX 可以加一个属...

linong11阅读 487

万字长文~vue+express+mysql带你彻底搞懂项目中的权限控制(附所有源码)
所谓的权限,其实指的就是:用户是否能看到,以及是否允许其对数据进行增删改查的操作,因为现在开发项目的主流方式是前后端分离,所以整个项目的权限是后端权限控制搭配前端权限控制共同实现的

水冗水孚11阅读 1.5k

如何写一个让面试官满意的 Generator 执行器?
例子都可以在 Console 中运行的(谷歌版本 76.0.3809.100),都是以最新浏览器支持的 JavaScript 特性来编写的,不考虑兼容性。

Samon12阅读 3.8k

一个互联网从业者,一起聊工作,聊生活。

1 声望
0 粉丝
宣传栏