【MySQL】 InnoDB的表空间

Metadata

title: 【MySQL】 InnoDB的表空间
date: 2023-06-22 05:36
tags:
  - 行动阶段/完成
  - 主题场景/数据存储
  - 笔记空间/KnowladgeSpace/ProgramSpace/BasicsSpace
  - 细化主题/数据存储
categories:
  - 数据存储
keywords:
  - 数据存储
description: 【MySQL】 InnoDB的表空间

概述

独立表空间

  1. : 连续的64个页就是一个区,也就是说一个区默认占用1MB空间大小。每256个区被划分成一组。
    • 第一个组最开始的3个页面的类型是固定的,也就是说extent 0 这个区最开始的3个页面的类型是固定的,分别是:
      • FSP_HDR 类型: 这个类型的页面是用来登记整个表空间的一些整体属性以及本组所有的区,也就是extent 0 ~ extent 255 这256个区的属性
      • IBUF_BITMAP 类型: 这个类型的页面是存储本组所有的区的所有页面关于INSERT BUFFER 的信息
      • INODE 类型: 这个类型的页面存储了许多称为INODE 的数据结构
    • 其余各组最开始的2个页面的类型是固定的,也就是说extent 256 、extent 512 这些区最开始的2个页面的类型是固定的,分别是:
      • XDES 类型: 全称是extent descriptor ,用来登记本组256个区的属性,对于在extent 256区中的该类型页面存储的就是extent 256 ~ extent 511 这些区的属性
      • IBUF_BITMAP 类型: 这个类型的页面是存储本组所有的区的所有页面关于INSERT BUFFER 的信息
  2. : 一个索引会生成2个段,一个叶子节点段,一个非叶子节点段。 定义了一个INODE Entry 结构来记录一下段中的属性
    • Segment ID 这个INODE Entry 结构对应的段的编号(ID)
    • NOT_FULL_N_USED 是在NOT_FULL 链表中已经使用了多少个页面。下次从NOT_FULL 链表分配空闲页面时可以直接根据这个字段的值定位到。
    • 3个List Base Node 分别为段的FREE 链表NOT_FULL 链表FULL 链表定义了List Base Node
    • Magic Number 用来标记这个INODE Entry 是否已经被初始化
    • Fragment Array Entry 每个Fragment Array Entry 结构都对应着一个零散的页面,这个结构一共4个字节,表示一个零散页面的页号
  3. 碎片(fragment)区 有些页用于段A,有些页用于段B,有些页甚至哪个段都不属于
  4. 区的分类
    • FREE 空闲的区
    • FREE_FRAG **有剩余空间的碎片区
    • FULL_FRAG 没有剩余空间的碎片区
    • FSEG 附属于某个段的区
  5. XDES Entry 是一个40个字节的结构,大致分为4个部分,各个部分的释义如下:
    • Segment ID (8字节) 每一个段都有一个唯一的编号,用ID表示,此处的Segment ID 字段表示就是该区所在的段。
    • List Node (12字节) 这个部分可以将若干个XDES Entry 结构串联成一个链表
    • State (4字节)这个字段表明区的状态。
    • Page State Bitmap (16字节) 一个区默认有64个页,这128个比特位被划分为64个部分,每个部分2个比特位,对应区中的一个页。
  6. 链表基节点 每个链表都对应这么一个List Base Node 结构
    • List Length 表明该链表一共有多少节点,
    • First Node Page Number 和First Node Offset 表明该链表的头节点在表空间中的位置。
    • Last Node Page Number 和Last Node Offset 表明该链表的尾节点在表空间中的位置。

系统表空间

  1. 系统表空间与独立表空间的一个非常明显的不同之处就是在表空间开头有许多记录整个系统属性的页面
    • 某个表属于哪个表空间,表里边有多少列
    • 表对应的每一个列的类型是什么
    • 该表有多少索引,每个索引对应哪几个字段,该索引对应的根页面在哪个表空间的哪个页面
    • 该表有哪些外键,外键对应哪个表的哪些列
    • 某个表空间对应文件系统上文件路径是什么
    • balabala … 还有好多,不一一列举了
  2. SYS_TABLES表
  3. SYS_COLUMNS表
  4. SYS_FIELDS表
  5. Data Dictionary Header页面
    1. Max Row ID: 我们生成一个名为row_id 的列作为主键,这个Max Row ID 是全局共享的
    2. Max Table ID: InnoDB存储引擎中的所有的索引都对应一个唯一的ID
    3. Max Space ID: InnoDB存储引擎中的所有的表空间都对应一个唯一的ID
    4. Root of SYS_TABLES clust index :本字段代表SYS_TABLES 表聚簇索引的根页面的页号。
    5. Root of SYS_TABLE_IDS sec index :本字段代表SYS_TABLES 表为ID 列建立的二级索引的根页面的页号。
    6. Root of SYS_COLUMNS clust index :本字段代表SYS_COLUMNS 表聚簇索引的根页面的页号。
    7. Root of SYS_INDEXES clust index: 本字段代表SYS_INDEXES 表聚簇索引的根页面的页号。
    8. Root of SYS_FIELDS clust index : 本字段代表SYS_FIELDS 表聚簇索引的根页面的页号。

独立表空间结构

区(extent)的概念

对于16KB的页来说,连续的64个页就是一个区,也就是说一个区默认占用1MB空间大小。

每256个区被划分成一组。

第一个组最开始的3个页面的类型是固定的,也就是说extent 0 这个区最开始的3个页面的类型是固定的,分别是:

  • FSP_HDR 类型: 这个类型的页面是用来登记整个表空间的一些整体属性以及本组所有的区,也就是extent 0 ~ extent 255 这256个区的属性
  • IBUF_BITMAP 类型: 这个类型的页面是存储本组所有的区的所有页面关于INSERT BUFFER 的信息。
  • INODE 类型: 这个类型的页面存储了许多称为INODE 的数据结构

其余各组最开始的2个页面的类型是固定的,也就是说extent 256 、extent 512 这些区最开始的2个页面的类型是固定的,分别是:

  • XDES 类型: 全称是extent descriptor ,用来登记本组256个区的属性,对于在extent 256区中的该类型页面存储的就是extent 256 ~ extent 511 这些区的属性
  • IBUF_BITMAP 类型: 这个类型的页面是存储本组所有的区的所有页面关于INSERT BUFFER 的信息。

段(segment)的概念

在表中数据量大的时候,为某个索引分配空间的时候就不再按照页为单位分配了,而是按照区为单位分配,甚至在表中的数据十分非常特别多的时候,可以一次性分配多个连续的区。

叶子节点有自己独有的区,非叶子节点也有自己独有的区。

存放叶子节点的区的集合就算是一个段( segment ),存放非叶子节点的区的集合也算是一个段。

说一个索引会生成2个段,一个叶子节点段,一个非叶子节点段。

为了考虑以完整的区为单位分配给某个段对于数据量较小的表太浪费存储空间的这种情况

设计InnoDB 的大叔们提出了一个碎片(fragment)区的概念

在一个碎片区中,并不是所有的页都是为了存储同一个段的数据而存在的,而是碎片区中的页可以用于不同的目的,比如有些页用于段A,有些页用于段B,有些页甚至哪个段都不属于

碎片区直属于表空间,并不属于任何一个段。

此后为某个段分配存储空间的策略是这样的:

  • 在刚开始向表中插入数据的时候,段是从某个碎片区以单个页面为单位来分配存储空间的。
  • 当某个段已经占用了32个碎片区页面之后,就会以完整的区为单位来分配存储空间。

区的分类

  • FREE 空闲的区: 现在还没有用到这个区中的任何页面。
  • FREE_FRAG 有剩余空间的碎片区: 表示碎片区中还有可用的页面。
  • FULL_FRAG 没有剩余空间的碎片区: 表示碎片区中的所有页面都被使用,没有空闲页面。
  • FSEG 附属于某个段的区: 每一个索引都可以分为叶子节点段和非叶子节点段,除此之外InnoDB还会另外定义一些特殊作用的段,在这些段中的数据量很大时将使用区来作为基本的分配单位。

这4种类型的区也可以被称为区的4种状态( State )

处于FREE 、FREE_FRAG 以及FULL_FRAG 这三种状态的区都是独立的,算是直属于表空间;而处于FSEG 状态的区是附属于某个段的。

为了方便管理这些区,设计InnoDB 的大叔设计了一个称为XDES Entry 的结构(全称就是Extent Descriptor Entry),每一个区都对应着一个XDES Entry 结构

XDES Entry 是一个40个字节的结构,大致分为4个部分,各个部分的释义如下:

  • Segment ID (8字节)
    每一个段都有一个唯一的编号,用ID表示,此处的Segment ID 字段表示就是该区所在的段。
  • List Node (12字节)
    这个部分可以将若干个XDES Entry 结构串联成一个链表
  • State (4字节)
    这个字段表明区的状态。
  • Page State Bitmap (16字节)
    一个区默认有64个页,这128个比特位被划分为64个部分,每个部分2个比特位,对应区中的一个页。

XDES Entry链表

  • 当段中数据较少的时候,会进行以下操作:
    • 首先查看表空间中是否有状态为FREE_FRAG的区;
    • 如果找到了FREE_FRAG的区,从该区中取一些零碎的页,将数据插入其中;
    • 如果没有找到FREE_FRAG的区,则在表空间下申请一个状态为FREE的区;
    • 将新申请的区的状态变为FREE_FRAG,并从该区中取一些零碎的页,将数据插入其中。
  • 不同的段在使用零碎页的时候,都会从上述区中取;
  • 直到该区中没有空闲空间,该区的状态变为FULL_FRAG。

ListNode 的作用

  • 把状态为FREE的区对应的XDESEntry结构通过ListNode来连接成一个链表,这个链表我们就称之为FREE链表。
  • 把状态为FREE_FRAG的区对应的XDESEntry结构通过ListNode来连接成一个链表,这个链表我们就称之为FREE_FRAG链表。
  • 把状态为FULL_FRAG的区对应的XDESEntry结构通过ListNode来连接成一个链表,这个链表我们就称之为FULL_FRAG链表。

为每个段中的区对应的XDES Entry 结构建立了三个链表:

  • FREE 链表:同一个段中,所有页面都是空闲的区对应的XDES Entry 结构会被加入到这个链表。注意和直属于表空间的FREE 链表区别开了,此处的FREE 链表是附属于某个段的。
  • NOT_FULL 链表:同一个段中,仍有空闲空间的区对应的XDES Entry 结构会被加入到这个链表。
  • FULL 链表:同一个段中,已经没有空闲空间的区对应的XDES Entry 结构会被加入到这个链表。

每一个索引都对应两个段,每个段都会维护上述的3个链表

链表基节点

这个结构中包含了链表的头节点和尾节点的指针以及这个链表中包含了多少节点的信息

每个链表都对应这么一个List Base Node 结构

  • List Length 表明该链表一共有多少节点,
  • First Node Page Number 和First Node Offset 表明该链表的头节点在表空间中的位置。
  • Last Node Page Number 和Last Node Offset 表明该链表的尾节点在表空间中的位置。

段的结构

段其实不对应表空间中某一个连续的物理区域,而是一个逻辑上的概念

定义了一个INODE Entry 结构来记录一下段中的属性

  • Segment ID
    这个INODE Entry 结构对应的段的编号(ID)
  • NOT_FULL_N_USED
    是在NOT_FULL 链表中已经使用了多少个页面。
    下次从NOT_FULL 链表分配空闲页面时可以直接根据这个字段的值定位到。
  • 3个List Base Node
    分别为段的FREE 链表、NOT_FULL 链表、FULL 链表定义了List Base Node
  • Magic Number
    用来标记这个INODE Entry 是否已经被初始化了
  • Fragment Array Entry
    每个Fragment Array Entry 结构都对应着一个零散的页面,这个结构一共4个字节,表示一个零散页面的页号。

各类型页面详细情况

FSP_HDR 类型

表空间的第一个页面,页号为0 。这个页面的类型是FSP_HDR

存储了表空间的一些整体属性以及第一个组内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字节 “这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链表的基节点

XDES Entry部分

每一个XDES Entry 结构对应表空间的一个区

每组开头的一个页面记录着本组内所有的区对应的XDES Entry 结构。

整个表空间的第一个页面,记录本组中的所有区对应的XDES Entry 结构以外,还记录着表空间的一些整体属性,这个页面的类型就是我们刚刚说完的FSP_HDR 类型,整个表空间里只有一个这个类型的页面

除去第一个分组以外,之后的每个分组的第一个页面只需要记录本组内所有的区对应的XDES Entry 结构即可

IBUF_BITMAP 类型

每个分组的第二个页面的类型都是IBUF_BITMAP

这种类型的页里边记录了一些有关Change Buffer 的东东

INODE 类型

第一个分组的第三个页面的类型是INODE

为了方便管理,他们又为每个段设计了一个INODE Entry 结构,这个结构中记录了关于这个段的相关属性。

undefined

从图中可以看出,一个INODE 类型的页面是由这几部分构成的:

名称 中文名 占用空间大小 简单描述
File Header 文件头部 38 字节 页的一些通用信息
List Node for INODE Page List 通用链表节点 12字节 存储上一个INODE页面和下一个INODE页面的指针
INODE Entry 段描述信息 16128 字节
Empty Space 尚未使用空间 6字节 “用于页结构的填充,没啥实际意义”
File Trailer 文件尾部 8字节 校验页是否完整
  • SEG_INODES_FULL 链表
    该链表中的INODE 类型的页面中已经没有空闲空间来存储额外的INODE Entry 结构了。
  • SEG_INODES_FREE 链表
    该链表中的INODE 类型的页面中还有空闲空间来存储额外的INODE Entry 结构了。

每当我们新创建一个段(创建索引时就会创建段)时,都会创建一个INODE Entry 结构与之对应,存储INODE Entry 的大致过程就是这样的:

  • 先看看SEG_INODES_FREE 链表是否为空,如果不为空,直接从该链表中获取一个节点,也就相当于获取到一个仍有空闲空间的INODE 类型的页面,然后把该INODE Entry 结构防到该页面中。当该页面中无剩余空间时,就把该页放到SEG_INODES_FULL 链表中。
  • 如果SEG_INODES_FREE 链表为空,则需要从表空间的FREE_FRAG 链表中申请一个页面,修改该页面的类型为INODE ,把该页面放到SEG_INODES_FREE 链表中,与此同时把该INODE Entry 结构放入该页面。

Segment Header 结构的运用

其中的PAGE_BTR_SEG_LEAF 和PAGE_BTR_SEG_TOP 都占用10个字节,它们其实对应一个叫Segment Header 的结构,该结构图示如下:

系统表空间

系统表空间的整体结构

系统表空间与独立表空间的一个非常明显的不同之处就是在表空间开头有许多记录整个系统属性的页面

InnoDB数据字典

MySQL除了保存着我们插入的用户数据之外,还需要保存许多额外的信息

  • 某个表属于哪个表空间,表里边有多少列
  • 表对应的每一个列的类型是什么
  • 该表有多少索引,每个索引对应哪几个字段,该索引对应的根页面在哪个表空间的哪个页面
  • 该表有哪些外键,外键对应哪个表的哪些列
  • 某个表空间对应文件系统上文件路径是什么
  • balabala … 还有好多,不一一列举了

为了更好的管理我们这些用户数据而不得已引入的一些额外数据,这些数据也称为元数据

InnoDB存储引擎特意定义了一些列的内部系统表(internal system table)来记录这些这些元数据:

这些系统表也被称为数据字典,它们都是以B+ 树的形式保存在系统表空间的某些页面

SYS_TABLES表

这个SYS_TABLES 表有两个索引

  • 以NAME 列为主键的聚簇索引
  • 以ID 列建立的二级索引
SYS_COLUMNS表


SYS_FIELDS表

Data Dictionary Header页面

  • Max Row ID:

如果我们不显式的为表定义主键,而且表中也没有UNIQUE 索引
InnoDB 存储引擎会默认为我们生成一个名为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,然后自增本字段的值。

  • 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 表聚簇索引的根页面的页号。