InnoDB 空间文件的布局

空间简介

InnoDB 的数据存储模型使用的是 空间 的概念,在 MySQL 环境下也称 表空间,有时在 InnoDB 中称为文件空间。

一个空间在操作系统层面可以包含多个真实的文件,如 ibdata1ibdata2 等。但它只是一个 单一的逻辑文件,多个物理文件会被认为是以物理的方式 连接 在一起的。

空间编号:InnoDB 给每个空间都分配了一个 ID,是一个 32 位的整数,在多个场合用来指代特定的空间。

系统空间

InnoDB 始终有一个 系统空间,其 ID 总是为 0。系统空间用来保存 InnoDB 所需的各种统计信息。

MVCC :Multi-Version Concurrency Control 多版本并发控制

ACID :数据库管理系统在写入或更新资料的过程中,为保证事务是正确可靠的,所必须具备的四个特性:原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。

除了用表格进行数据存储,InnoDB 还需要对表格的元数据进行查找,保存、读取 MVCC 信息,以遵守 ACID 及事务隔离。

系统表空间是一个共享的表空间,由多个表共享。它可以由一个或多个数据文件来代表,默认是一个名为 ibdata1 的数据文件,创建于 MySQL 的数据目录。系统数据文件的大小和数量由 innodb_data_file_path 选项控制。

一表一文件空间

file-per-table tablespace

在以前较低的版本中,InnoDB 的表格和索引是保存在系统表空间中的。当时的假设是:服务器是专门处理数据库的,对于数据的增长进行了精心的准备,为 MySQL 分配的任何磁盘空间都不会用于其它用途。

一表一文件的表空间更加灵活,每个表格及其索引都保存在单独的 .ibd 数据文件中。每个数据文件都代表一个单独的表空间。

一表一文件由选项 inoodb_file_per_table 控制,默认为开启。

一般表空间

general tablespace

从 MySQL 5.7.6 起,引入一般表空间。

一般表空间是共享的表空间,由 CREATE TABLESPACE 创建。可以在 MySQL 数据目录之外创建。一个一般表空间可以保存多个表格,支持所有的行格式。

临时表空间

temporary tablespace

从 MySQL 5.7 起,引入临时表空间。

临时表:表格中的数据不需要永久保存。例如,可以是复杂计算、复杂转换其中间步骤的结果,这个中间的数据即使遇到崩溃也不需要恢复。

临时表空间用于保存非压缩的临时表,及其相关对象。

使用 innodb_temp_data_file_path 配置文件选项来定义相对路径、文件名、大小等临时表空间数据文件的属性。如果没有指定,默认会在数据目录中创建名为 ibtmp1 的数据文件,文件大小为 12 MB,该文件会自动扩展。

每次服务器启动,该文件都会重新创建,并会收到一个动态的空间 ID。

正常关机或退出时,临时表空间会被删除。发生崩溃不会自动删除。

空间管理

Pages

每个空间都被划分为多个页面,通常每个页面的大小为 16 KB。

页面编号 :空间中的每个页面都被分配给一个 4 字节的编号,也称为 偏移量,即从空间起始位置算起,该页面的偏移量。

因此,0 号页面位于偏移量 0,1 号页面位于偏移量 16384,即 16 KB,依此类推。

因为页面编号为 32 位,因此每个空间最多可以有 232 个页面,所以一个空间的最大容量为 64 TB:

232 x 16 KB = 64 TB

页面的基本结构

image-center

  • 页头:FIL Header,即 File Header,38 字节。
  • 页尾:FIL Trailer,即 File Trailer,8 字节。

页头页尾之外的部分,具体要保存什么内容,取决于页面的类型,共有 16,338 字节可用。

页头、页尾的结构

image-center

页头结构
  • 校验和
  • 页面编号:对从某个区域读取的页面编号与其所在偏移量进行检查,看是否匹配,这有助于判断读取是否正确。
  • 指针:上一页、下一页的指针。借此可以生成双链接的页面列表,指针可在索引页中用来链接同一级别的所有页面,使得全表索引扫描更加高效。有些页面类型不使用指针。
  • 日志序号:本页面最近被修改的日志序号,即 LSN,Log Sequence Number。同一个 LSN 的后 32 位保存于文件尾。
  • 页面类型:保存在文件头。该字段决定了页面其它部分的结构。需要根据页面类型来解析页面数据的其余部分。分配页面主要用于文件空间管理、extent 管理、事务系统、数据字典、undo 日志、BLOB、索引。
  • 刷新 LSN:即 Flush LSN,保存于文件头。
  • 空间 ID: 保存在文件头。
页尾结构
  • 校验和:旧格式的校验和。
  • 日志序号:页头 LSN 的后 32 位。

Extent

一个表空间文件相当于众多的页面连接起来组成的。为了实现更加高效的管理,把 64 个连续的页面放到一起组成一个 extent,即 ,大小刚好为 1 MB。在一个空间中,许多结构只使用节来分配页面。

😈 从文件系统开始,extent 就没有一个合适的中文词汇来描述。到这里,本人突然觉得可以从页面出发,下一个比页面大的与书有关的名词,“章、篇” 都太大,“节” 比较合适。因此,在我自己的文字中,本人暂且把 extent 叫做

Segment

表空间中的 文件 称为段。

当表空间中的段变大时,InnoDB 第一次会为其分配 32 个页。之后,InnoDB 开始为其分配整个的节,如果文件很大,一次最多可以增加 4 个节,以保证数据的连续性。

会为每个索引分配两个段,一个给 B-tree 的非叶子节点,另一个给叶子节点。保持叶子节点在磁盘中的连续更利于高效 I/O,因为这些叶子节点包含真正的数据。

表空间中的某些页面中包含其它页面的位图,因此有一些节无法整体分配给段,只能以单独的页面来分配。

当使用 SHOW TABLE STATUS 语句查看表空间的可用空间时,InnoDB 会报告表空间中可用的节的数量。有一些节是系统保留的,用于清理操作和其它内部事务,这些节不会算在可用空间中。

从表格删除数据时,InnoDB 会收缩对应的 B-tree 索引。腾出来的空间是否会变成可用空间给别人使用,这要取决于该删除的操作释放的是单独的页还是节。如果删除一个表格或删除表格所有的行,系统保证会释放其空间给其它用户,但要记住一点,只有在清理操作完成后,这些记录才被物理删除。

清理:purge,是一种清除垃圾数据的操作,由一个或多个单独的后台线程执行,以一定周期循环进行。清理会从历史列表中解析并处理 undo log 页面,旨在清除那些 标记为已删除 的聚集索引和二级索引记录。该删除标记通常是由 DELETE 语句产生的,这些记录对于 MVCC 或回滚都不再需要了。清理完成后,undo log 的页面被释放。

空间文件的结构

基本结构

InnoDB 需要统计一些特定的信息,以便跟踪所有的页、节,及空间自己。

image-center

第一页:文件空间头

FSP_HDR:Filespace Header / Extent Descriptor

Extent Descriptor 简称 XDES。

空间中的第一个页面为 0 号,用作文件空间头,其中包含的 FSP 头结构用于追踪多项信息:空间大小、可用节列表、节碎片、完整节等。

0 号页面只能保存其后的 256 个节的统计信息。因此,后面的空间每 256 个节就必须分配一个页,专门用来保存这 256 个节的统计信息,这个页就是 XDES 页,它和 0 号页面 FSP_HDR 的结构是一样的。

随着空间文件的增长,会自动分配更多的页面。

第二页:插入缓冲位图

IBUF_BITMAP:Insert Buffer Bookkeeping

第二页,即 1 号页面,用于保存与插入缓冲相关的统计信息。

第三页:

INODE:Index Node Information

第三页,即 2 号页面,是用于保存与段(segment)相关的列表。

每个 INODE 页面可以保存 85 个 INODE 条目,每个索引需要两个条目。

系统空间的结构

0 号空间是系统空间,包含多个系统预留的固定编号的页面,专门用来保存大量的关键信息。

做为系统空间,前三页的结构与普通空间一样,每四页之后,与独立表空间就不太一样了。

image-center

  1. 第 3 页:SYS 类,插入缓存头
  2. 第 4 页:INDEX 类,插入缓存的索引结构的根页,插入缓存也是一个 B+树;
  3. 第 5 页:TRX_SYS 类,事务系统头。如最近一次的事务 ID,MySQL 二进制日志信息,及双写缓冲区的位置信息;
  4. 第 6 页:SYS 类,第一个回滚段。
  5. 第 7 页:SYS 类,数据字典头。包含了数据字典中所有表格根页面的个数,需要这些信息来访问数据字典表页。
  6. 第 64-127 页:双写缓冲区第一块(64 页,即一个节)。双写缓冲区是 InnoDB 的一个恢复机制;
  7. 第 128-191 页:双写缓冲区的第二块。

系统表空间的其他页,会在需要时分配给索引、回滚段、undo 日志等。

数据字典

Data dictionary

由系统内部表格组成,用于保存表格、索引、字段等对象的元数据信息。

双写缓冲区

Double write buffer

InnoDB 在把缓冲池写入数据文件的正确位置之前,会先把它们先写入双写缓冲区的页面中。

如果在写入页面期间,mysqld 进程崩溃了,InnoDB 进入崩溃自动恢复时,会从双写缓冲区中找到该页面的完好的副本。

修改缓冲区

Chage Buffer

修改缓冲区是一个特殊的数据结构,如果受影响的页面不在缓冲池中,对二级索引页面的修改就会被缓存到修改缓冲区中。

Undo 日志

Undo 日志用于保存当前正被某个事务所修改的数据的副本。这样,如果同时有另一个事务要查询该数据修改之前的内容,Unod 日志就派上用场了。所以 Undo 日志也称为回滚片断,即 Rollback Segments。

默认情况下,undo 日志是系统表空间的物理组成部分。在早期的版本中,undo 日志是不可见的,因为它位于系统表空间中。但在 MySQL 5.6 版本以后,可以自行创建多个单独的 undo 日志文件,即表空间文件。

表格及索引数据

除了以上内容,系统表空间还保存了多个表和索引数据。

独立空间的结构

启用 file-per-table 之后,InnoDB 会为创建的每个表格都创建一个对应的、独立的表空间文件,后缀为 .ibd

image-center

独立的空间文件结构很简单,没有系统空间存储数据类型种类多。

前三页分别是 FSP_HDR,IBUF_BITMAP 和 INODE 页,从第四页开始存储索引页。

在 InnoDB 中,B+树 内部节点页和叶子节点页统称为索引页。第四页固定为主键的根页,其他页则可能是内部节点页、主键叶子节点、辅助索引根页,辅助索引内部节点页、辅助索引叶子节点页。

因为大部分 InnoDB 系统信息都保存在系统空间中,所以独立空间大部分都是 INDEX 类型的页,用来保存表数据。