MySQL InnoDB 磁盘结构

254 阅读13分钟

以下图表展示了组成 InnoDB 存储引擎架构的内存结构和磁盘结构。

image.png

1. 表 Tables

2. 索引 Indexes

2.1 聚集索引和二级索引

每个 InnoDB 表都有一个特殊的索引,称为聚簇索引,用于存储行数据。通常,聚簇索引与主键同义。为了在查询、插入和其他数据库操作中获得最佳性能,理解 InnoDB 如何使用聚簇索引优化常见的查找和 DML(数据操作语言)操作至关重要。

聚簇索引的定义

  1. 主键作为聚簇索引
    当您在表上定义主键(PRIMARY KEY)时,InnoDB 将其用作聚簇索引。建议每个表都定义一个主键。如果没有逻辑上唯一且非空的列或列组可以用作主键,可以添加一个自增列。自增列的值是唯一的,并会在插入新行时自动增加。

  2. 没有主键的情况
    如果表中未定义主键,InnoDB 会使用第一个定义为 NOT NULL 的唯一索引(UNIQUE)作为聚簇索引。

  3. 没有主键或适当的唯一索引的情况
    如果表既没有主键也没有合适的唯一索引,InnoDB 会生成一个隐藏的聚簇索引,名为 GEN_CLUST_INDEX,该索引基于一个包含行 ID 值的合成列。行 ID 是一个 6 字节的字段,在插入新行时会单调递增。因此,这些行根据行 ID 的顺序物理存储,也即插入顺序。

聚簇索引如何加速查询

通过聚簇索引访问行数据速度很快,因为索引搜索直接定位到包含行数据的页。如果表很大,与其他存储组织方式(将行数据存储在与索引记录不同的页中)相比,聚簇索引架构通常可以节省一次磁盘 I/O 操作。

辅助索引与聚簇索引的关系

聚簇索引之外的索引称为辅助索引。在 InnoDB 中,每个辅助索引记录除了包含辅助索引指定的列外,还包含该行的主键列值。InnoDB 使用这个主键值在聚簇索引中查找对应的行。

如果主键较长,辅助索引会占用更多空间,因此推荐使用较短的主键。

2.2 InnoDB 索引的物理结构

除空间索引以外,InnoDB 索引均采用 B+ 树数据结构。空间索引则使用 R 树,这是一种专门用于索引多维数据的特殊数据结构。索引记录存储在其 B+ 树或 R 树数据结构的叶子页中。默认情况下,索引页的大小为 16KB,该页大小由 MySQL 实例初始化时的 innodb_page_size 设置决定。详情参见 InnoDB 启动配置

当新记录插入到 InnoDB 的聚簇索引中时,InnoDB 会尝试保留每页空间的 1/16 用于将来对索引记录的插入和更新。如果索引记录按顺序(升序或降序)插入,则生成的索引页大约充满 15/16。如果记录以随机顺序插入,则页的填充率会在 1/2 到 15/16 之间。

在创建或重建 B+ 树索引时,InnoDB 会执行批量加载操作。这种索引创建方法称为排序索引构建(sorted index build)。innodb_fill_factor 变量定义了在排序索引构建过程中,每个 B+ 树页被填充的百分比,其余空间保留用于未来的索引增长。排序索引构建不支持空间索引。更多信息请参见 排序索引构建。innodb_fill_factor 设置为 100 时,聚簇索引页会保留 1/16 的空间以供未来索引增长使用。

如果 InnoDB 索引页的填充因子低于 MERGE_THRESHOLD(默认为 50%),InnoDB 会尝试收缩索引树以释放页空间。MERGE_THRESHOLD 设置同时适用于 B+ 树和 R 树索引。更多信息请参见 索引页合并阈值的配置

3. 表空间 Tablespaces

3.1 系统表空间 The System Tablespace

系统表空间是用于存储变更缓冲区(change buffer)的区域。系统表空间还可以包含表和索引数据,如果表是在系统表空间中创建的,而不是在每表一个表空间(file-per-table tablespaces)或一般表空间(general tablespaces)中创建。

在 MySQL 早期版本中,系统表空间还包含了 InnoDB 数据字典。从 MySQL 8.0 开始,InnoDB 将元数据存储在 MySQL 数据字典中(参见第 16 章,MySQL 数据字典)。此外,在旧版本中,系统表空间还包含 双写缓冲区(doublewrite buffer) ,从 MySQL 8.0.20 开始,这部分存储被迁移到独立的双写文件中(详见 17.6.4 双写缓冲区)。

系统表空间可以由一个或多个数据文件组成。默认情况下,系统表空间只有一个数据文件,名为 ibdata1,存储在数据目录中。系统表空间数据文件的大小和数量由 innodb_data_file_path 启动选项定义(配置方法详见 系统表空间数据文件配置)。

3.2 独立表空间 File-Per-Table Tablespaces

独立表空间(file-per-table tablespace)包含单个 InnoDB 表的数据和索引,并以单个数据文件的形式存储在文件系统中。

image.png

3.3 通用表空间 General Tablespaces

通用表空间提供以下功能:

  1. 类似于系统表空间

通用表空间是一种共享表空间,可以存储多个表的数据,与系统表空间类似。

  1. 内存优势

与每表一个表空间(file-per-table tablespaces)相比,通用表空间可能具有内存优势。

服务器会在表空间的生命周期内将其元数据保存在内存中。将多个表存储在较少的通用表空间中,所消耗的表空间元数据内存比将相同数量的表分别存储在多个每表一个表空间中更少。

  1. 灵活的数据文件存储位置

通用表空间的数据文件可以存放在相对于 MySQL 数据目录的路径中,也可以存放在独立于数据目录的位置。这提供了与每表一个表空间类似的数据文件和存储管理能力。例如,您可以:

• 独立管理关键表的性能

• 为特定表设置 RAID 或 DRBD

• 将表绑定到特定磁盘

  1. 支持所有表行格式及相关功能

通用表空间支持所有表的行格式及其关联的特性。

  1. 创建表时的 TABLESPACE 选项

在使用 CREATE TABLE 创建表时,可以通过 TABLESPACE 选项将表创建在通用表空间、每表一个表空间或系统表空间中。

  1. 移动表时的 TABLESPACE 选项

在使用 ALTER TABLE 时,可以通过 TABLESPACE 选项在通用表空间、每表一个表空间和系统表空间之间移动表。

3.4 撤销表空间 Undo Tablespaces

撤销表空间(Undo Tablespace)包含撤销日志,这些日志是一组记录,存储了关于如何撤销事务对聚簇索引记录所做的最新更改的信息。

3.5 临时表空间 Temporary Tablespaces

会话临时表空间

会话临时表空间用于存储用户创建的临时表以及优化器在 InnoDB 被配置为磁盘内部临时表的存储引擎时创建的内部临时表。从 MySQL 8.0.16 开始,磁盘内部临时表的存储引擎为 InnoDB(之前由 internal_tmp_disk_storage_engine 参数决定存储引擎)。

会话临时表空间在会话第一次请求创建磁盘临时表时从一个临时表空间池中分配。每个会话最多可以分配两个表空间,一个用于用户创建的临时表,另一个用于优化器创建的内部临时表。会话所分配的临时表空间用于该会话创建的所有磁盘临时表。当会话断开连接时,其临时表空间会被截断并归还到池中。服务器启动时会创建一个包含 10 个临时表空间的池。池的大小不会缩小,并会根据需要自动添加表空间。在正常关闭或初始化中断时,临时表空间池会被删除。会话临时表空间文件初始大小为 5 页,并带有 .ibt 文件扩展名。

会话临时表空间保留了 40 万个空间 ID 的范围。由于每次服务器启动时会重新创建会话临时表空间,因此这些空间 ID 在服务器关闭时不会持久化,可能会被重复使用。

全局临时表空间

全局临时表空间(ibtmp1)用于存储对用户创建的临时表所做更改的回滚段。

innodb_temp_data_file_path 变量定义了全局临时表空间数据文件的相对路径、名称、大小及属性。如果未为该变量指定值,则默认会在 innodb_data_home_dir 目录中创建一个名为 ibtmp1 的自动扩展数据文件,其初始大小稍大于 12MB。

全局临时表空间在正常关闭或初始化中断时被删除,并在每次服务器启动时重新创建。创建时会为其分配动态生成的空间 ID。如果无法创建全局临时表空间,启动将被拒绝。如果服务器意外停止,全局临时表空间不会被移除。在这种情况下,数据库管理员可以手动删除全局临时表空间,或重启 MySQL 服务器。重启服务器会自动移除并重新创建全局临时表空间。

4. 双写缓冲区 Doublewrite Buffer

双写缓冲区是一块存储区域,InnoDB 在将页面写入到其在 InnoDB 数据文件中的正确位置之前,会先将从缓冲池刷新的页面写入该缓冲区。如果在页面写入过程中发生操作系统、存储子系统错误或 mysqld 进程意外退出,InnoDB 可以在崩溃恢复期间从双写缓冲区中找到页面的有效副本。

尽管数据会被写入两次,但双写缓冲区并不会带来两倍的 I/O 开销或 I/O 操作次数。数据以大的顺序块写入双写缓冲区,并通过单个 fsync() 调用将其刷入操作系统(除非 innodb_flush_method 设置为 O_DIRECT_NO_FSYNC)。

在 MySQL 8.0.20 之前,双写缓冲区的存储区域位于 InnoDB 系统表空间中。从 MySQL 8.0.20 开始,双写缓冲区存储区域位于双写文件中。

以下变量用于配置双写缓冲区:

1. innodb_doublewrite

• 该变量控制是否启用双写缓冲区。默认情况下,双写缓冲区在大多数情况下是启用的。

• 如果希望更关注性能而非数据完整性(如进行基准测试时),可以将 innodb_doublewrite 设置为 OFF 以禁用双写缓冲区。

• 从 MySQL 8.0.30 开始,innodb_doublewrite 支持以下设置:

• DETECT_AND_RECOVER:等同于 ON,全面启用双写缓冲区。数据库页面内容写入双写缓冲区,在恢复时使用该缓冲区修复不完整的页面写入。

• DETECT_ONLY:仅将元数据写入双写缓冲区,不写入数据库页面内容,恢复时不使用双写缓冲区修复页面写入,仅用于检测不完整的页面写入。

• MySQL 8.0.30 起支持动态更改 innodb_doublewrite 设置(ON、DETECT_AND_RECOVER 和 DETECT_ONLY 之间)。但不支持在启用双写缓冲区和 OFF 之间的动态切换。

• 如果双写缓冲区位于支持原子写入的 Fusion-io 设备上,则双写缓冲区会自动禁用,数据文件写入使用 Fusion-io 的原子写入。

2. innodb_doublewrite_dir

• 此变量(在 MySQL 8.0.20 引入)定义 InnoDB 创建双写文件的目录。

• 如果未指定目录,双写文件会在 innodb_data_home_dir 目录下创建,默认为数据目录。

• 推荐将双写文件目录放置在最快的存储介质上。

3. innodb_doublewrite_files

• 定义双写文件的数量。默认情况下,每个缓冲池实例创建两个双写文件:一个用于缓冲池的刷写列表,另一个用于缓冲池的 LRU 列表。

• 双写文件的命名格式为:#ib_page_size_file_number.dblwr(若设置为 DETECT_ONLY 则为 .bdblwr)。

4. innodb_doublewrite_pages

• 控制每个线程的双写页面最大数量。默认值为 innodb_write_io_threads 的值。

• 此变量适用于高级性能调优,大多数用户可以使用默认值。 其他特性

• 从 MySQL 8.0.23 开始,对于加密表空间,InnoDB 自动加密属于这些表空间的双写文件页面。

• 对于页面压缩表空间,双写文件页面会被压缩。

• 双写文件可以包含未加密、未压缩、加密、压缩,以及同时加密和压缩的页面。

5. Redo Log

Redo Log 是一种基于磁盘的数据结构,用于在崩溃恢复过程中纠正因未完成事务而写入的数据。在正常运行期间,重做日志记录由 SQL 语句或低级 API 调用引发的表数据更改请求。对于因意外关闭而未能完成更新数据文件的修改,会在初始化期间自动回放这些日志,并在接受连接之前完成相关恢复操作。关于重做日志在崩溃恢复中的作用,请参阅 第 17.18.2 节 “InnoDB 恢复”

Redo Log在磁盘上以Redo Log文件的形式物理存储。写入Redo Log文件的数据以受影响记录的形式编码,这些数据统称为 redo 。数据在重做日志文件中的流动通过一个不断增加的 LSN 值(日志序列号) 表示。随着数据修改的发生,重做日志数据被追加写入,同时随着检查点的推进,最旧的数据会被截断。

6. Undo Logs

撤销日志是一组与单个读写事务关联的撤销日志记录。每条撤销日志记录包含如何撤销事务对聚簇索引记录的最新更改的信息。如果另一个事务需要在一致性读取操作中查看原始数据,则未修改的数据将从撤销日志记录中检索。撤销日志存在于撤销日志段中,而撤销日志段包含在回滚段中。回滚段存放在撤销表空间以及全局临时表空间中。

存放在全局临时表空间中的撤销日志用于修改用户定义临时表数据的事务。这些撤销日志不会记录重做日志,因为它们不需要用于崩溃恢复。它们仅在服务器运行时用于回滚。这种类型的撤销日志通过避免重做日志的 I/O 操作提升了性能。

有关撤销日志静态加密的数据信息,请参阅[撤销日志加密](Undo Log Encryption)。

每个撤销表空间和全局临时表空间分别支持最多 128 个回滚段。参数 innodb_rollback_segments 用于定义回滚段的数量。

回滚段支持的事务数量取决于回滚段中的撤销槽数量以及每个事务所需的撤销日志数量。回滚段中的撤销槽数量根据 InnoDB 页大小而有所不同。