InnoDB的磁盘结构是一个分层、组织精密的体系,核心围绕着表空间(Tablespaces) 展开。它主要由以下几大部分组成:
核心思想:数据存储在表空间文件中!
-
表空间(Tablespaces)
- 定义: InnoDB存储引擎用于管理存储的逻辑结构,本质上是一个或多个物理磁盘文件(
.ibd)的集合。表空间是数据库数据和元数据的物理容器。 - 主要类型:
- 系统表空间(System Tablespace):
- 文件: 通常是一个名为
ibdata1(也可能有ibdata2等)的文件。 - 存储内容 (在未开启
innodb_file_per_table或特定元数据)- 数据字典(Data Dictionary): 包含关于表结构、列、索引、外键等数据库对象的元数据。
- Change Buffer: 用于加速对非唯一二级索引的插入、删除和更新操作(当目标页不在缓冲池中时)。
- Doublewrite Buffer: 关键机制,用于防止页部分写入(Partial Page Write)。在将页(Page)写入数据文件之前,会先将它们写入这个缓冲区。如果发生崩溃,可以利用这个缓冲区来恢复完整的页。
- Undo Log: (在 MySQL 5.6 之前以及 5.7.1 之后可以配置,默认在系统表空间)** 记录数据修改前的值,用于事务回滚(ROLLBACK)和多版本并发控制(MVCC)。
- 表数据(Table Data): 在 MySQL 5.6.6 之前,或即使开启了
innodb_file_per_table但未完全迁移完的表,数据会存储在系统表空间。 MySQL 5.6.6 引入innodb_file_per_table后,默认新创建的用户表的数据会存储在独立的表空间文件。
- 文件: 通常是一个名为
- 独立表空间/每表文件表空间(File-Per-Table Tablespaces):
- 文件: 每个用户表对应一个独立的
.ibd文件(存储在数据库目录下,如your_database/your_table.ibd)。 - 启用方式: 设置
innodb_file_per_table=ON(MySQL 5.6.6 及以后版本默认开启)。 - 存储内容:
- 该表的数据(Data): 即表中的行记录。
- 该表的索引(Indexes): 包括聚簇索引(主键索引)和所有二级索引。
- 该表的Undo Log: 该表关联的Undo日志段(自 MySQL 8.0 起)。
- 优点:
- 更好的空间管理: 可以单独压缩、备份、恢复、删除单个表空间文件。删除表时可以直接删除.ibd文件回收空间(系统表空间不行)。
- 性能提升: 可以减少碎片,优化 I/O。
- 便于迁移。
- 文件: 每个用户表对应一个独立的
- 通用表空间(General Tablespaces):
- 文件: 用户自定义的
.ibd文件(不在数据库目录下)。 - 创建方式:
CREATE TABLESPACE ... ADD DATAFILE ... - 存储内容: 用户可以指定将多个表存储在其中。
- 用途: 灵活管理存储,可以集中存放多个表,便于管理。支持压缩。
- 文件: 用户自定义的
- Undo 表空间(Undo Tablespaces):
- 文件: 通常命名为
undo_001,undo_002等(在MySQL数据目录或配置的innodb_undo_directory下)。 - 存储内容: 专门存放Undo日志记录。
- 启用: MySQL 5.6 开始引入配置,MySQL 5.7 开始推荐分离,MySQL 8.0 默认至少创建2个初始的Undo表空间并管理其生命周期。
- 目的: 将Undo日志从系统表空间中分离出来,提升管理灵活性和性能。支持在线回收Undo空间(truncate)。
- 文件: 通常命名为
- 临时表空间(Temporary Tablespaces):
- 文件: 默认有
ibtmp1。 - 存储内容: 存放临时表(用户创建的
CREATE TEMPORARY TABLE)和查询执行过程中内部临时表(internal temporary tables)的磁盘数据部分。 - 特点: 重启后会重建(数据丢失)。可以配置大小上限(
innodb_temp_data_file_path)。
- 文件: 默认有
- 系统表空间(System Tablespace):
- 定义: InnoDB存储引擎用于管理存储的逻辑结构,本质上是一个或多个物理磁盘文件(
-
表空间的内部结构:段、簇、页
每个表空间内部进一步被组织成更小的逻辑单元:- 段(Segment):
- 表空间的主要组织结构单元。
- 每个索引在表空间中占据两个主要的段:
- 叶子节点段(Leaf Segment / B-tree Leaf Node Segment): 存储索引的叶子节点(对于聚簇索引,叶子节点存储的是完整的行数据;对于二级索引,存储的是索引列和主键值)。
- 非叶子节点段(Non-Leaf Segment / B-tree Non-Leaf Node Segment): 存储索引的非叶子节点(即内部节点,用于导航定位到叶子节点)。
- 此外,还存在用于管理回滚段(Undo Segment)的段。回滚段本身包含多个Undo Slot。
- 段由簇(Cluster) 组成。
- 簇(Extent / Cluster):
- 组成: 由连续的页(Page) 组成。
- 大小: 固定为 1MB(在页大小为默认16KB的情况下,一个簇包含
1MB / 16KB = 64个连续的页)。 - 目的: 分配存储空间的基本单位(最初分配给段时,分配一个簇),减少碎片,提高I/O效率(连续存储)。
- 页(Page):
- 定义: InnoDB磁盘管理的最小单位。 所有数据的读写操作,都是以页为单位进行的。数据在写入磁盘之前必须先读入内存中的缓冲池(Buffer Pool)进行操作。
- 大小: 固定 16KB(可以通过编译时参数修改,但非常不推荐,兼容性问题多)。
- 类型: 根据存储内容的不同,页有多种类型:
- 数据页(Index Page / Data Page): 最常见的页类型,用于存储B+树索引的节点(叶子节点和非叶子节点),即实际的行数据和索引项。
- Undo页(Undo Page): 存储Undo日志记录。
- 系统页(System Page): 存储数据字典信息等(主要存在于系统表空间)。
- Insert Buffer Bitmap Page: 管理Change Buffer空间使用情况。
- Insert Buffer Free List Page: 管理Change Buffer的空闲列表。
- FSP_HDR (File Space Header Page): 表空间的第一个页,存储表空间整体元信息,如空间ID、页总数、簇使用情况等。
- INODE Page: 存储段(Segment)的信息(FSP_HDR页包含INODE页的指针)。
- XDES Page (Extent Descriptor Page): 跟踪簇的使用情况(FSP_HDR页包含第一个XDES页的指针)。
- 等等...
- 数据页(Index/Data Page)结构剖析: 这是理解InnoDB行存储的核心。
File Header(38B):记录页的通用信息,如页类型(INDEX)、前一页/后一页指针(构成双链表)、页的校验和(Checksum)、页在表空间中的位置(SPACE_ID & PAGE_NO)等。Page Header(56B):记录页的状态信息,如存储记录数、槽(Slot)数、堆(Heap)信息、垃圾记录链表头指针、上次插入位置、页面层级(在B+树中的深度)等。Infimum + Supremum Records(13B each):系统定义的虚拟最小记录(Infimum)和最大记录(Supremum)。用户记录都逻辑上位于两者之间,便于边界处理。-
User Records(行记录, Row Records): 实际存储行数据的区域。记录按主键顺序紧密存储(对于聚簇索引叶子页)。记录有特定的格式。 -
Free Space: 页中尚未使用的空间。删除记录或插入较小记录后,新记录可能会优先使用这里。 Page Directory(Slot Array):存储页内记录的相对位置(逻辑偏移量),结构是一个稀疏索引。每个槽(Slot)指向页内的一个记录(通常是按主键顺序每组最后几条记录中的一个)。用于页内快速二分查找定位记录。File Trailer(8B):校验点,包含页的校验和。页刷盘后,将此校验和与File Header中的校验和比对,确保页写入完整。
- 段(Segment):
-
行格式(Row Format)
- 决定了用户记录(User Records) 在数据页内是如何物理存储的。
- 常见格式 (由
ROW_FORMAT决定):-
REDUNDANT(过时): MySQL 5.0.3 之前的默认格式。格式最紧凑但效率相对较低。 -
COMPACT(流行): 比REDUNDANT更高效的存储格式。减少约20%的行空间,特别是对于可变长类型和NULL值。它将变长字段的长度信息集中放在行记录的开头。 -
DYNAMIC(默认): MySQL 5.7.9 (InnoDB 1.2) 及以后版本的默认格式。 是COMPACT格式的增强。主要改进在于处理大对象(BLOB,TEXT,JSON, 大的VARCHAR)。在这些列很大的情况下,DYNAMIC格式只在页中存储一个20字节的指针指向单独分配的"溢出页(Overflow Page)"存储实际的大值。而COMPACT会尝试在行内存储768字节的前缀。这使得DYNAMIC对于包含大对象的表更高效,显著减少了行溢出和碎片。 -
COMPRESSED: 类似于DYNAMIC,但增加了表/页级别的数据压缩功能(使用zlib)。适用于读密集、存储敏感的场景,牺牲一定CPU开销换取空间节省。
-
- 核心优化点:
DYNAMIC格式通过使用溢出页指针,极大地提升了大字段操作的效率和存储空间利用率,是现代应用推荐使用的格式。
-
索引组织存储
- 聚簇索引(Primary Key / Clustered Index):
- InnoDB表的数据存储方式基于聚簇索引组织。
- 每张表必须且只能有一个聚簇索引(主键索引)。
- 表的数据(行记录)物理上就是按照主键值的顺序存储在叶子节点上的。
- 非叶子节点存储主键值以及指向子节点的指针(页号+槽号)。
- 优点: 按主键检索非常高效(1-3次磁盘I/O通常可定位)。范围查询效率高(连续存储)。
- 缺点: 主键值大小影响所有二级索引的大小和性能(因为二级索引叶子节点存储主键值)。主键修改成本高(需要移动行)。
- 二级索引(Secondary Index / Non-Clustered Index):
- 叶子节点不存储完整的行数据。
- 叶子节点存储的是:
- 索引的列值 (The key columns of the secondary index)
- 对应行的主键值 (The primary key columns of the clustered index)
- 定位完整数据: 如果查询所需列不在二级索引覆盖的列中(即非覆盖索引查询),则需要通过二级索引叶子节点记录的主键值,回到聚簇索引(主键索引)中进行一次“回表”查找(通常通过B+树查找)来获取完整的行数据。这是影响二级索引效率的关键因素!
索引覆盖(Covering Index)可以避免回表。
- 没有显式定义主键怎么办?
- InnoDB会自动检查所有唯一非空索引,选择一个唯一的非空索引(第一个找到的)作为聚簇索引。
- 如果找不到这样的索引,InnoDB会隐式创建一个名为
GEN_CLUST_INDEX的自增ROWID(6字节)作为隐藏的主键(聚簇索引)。隐藏主键会记录在数据字典中,但对用户不可见,也会增加二级索引大小(叶子节点需要存储这个大的ROWID)。
- 聚簇索引(Primary Key / Clustered Index):
-
其他重要磁盘组件
- 重做日志文件(Redo Log Files):
- 文件: 通常命名为
ib_logfile0和ib_logfile1(默认两个,大小可配)。 - 存储内容: 顺序写入的日志记录,记录了物理层面上对数据页的修改操作(逻辑物理混合)。它是事务持久性的核心保证。
- 工作原理 (Write-Ahead Logging - WAL):
- 事务提交前,必须先将对数据页的所有修改记录写入(持久化到磁盘)Redo Log文件。
- 数据页的修改可能还停留在Buffer Pool中,没有写回磁盘数据文件(.ibd)。
- 如果发生崩溃,重启时通过重放(Replay)这些Redo Log记录,可以将已经提交的事务所做的修改重新应用到数据文件,保证持久性(Durability)。
- 循环写入: Redo Log文件是循环使用的。当写满最后一个文件时,会覆盖写第一个文件(前提是第一个文件中的修改对应的脏页已经被刷盘)。
- 与磁盘结构的关联: Redo Log记录了所有对数据页(最终存储在表空间文件中)的更改。
- 文件: 通常命名为
- Doublewrite Buffer (物理实现):
- 虽然逻辑上属于系统表空间的一部分,但它是一个特殊的磁盘区域。
- 在将脏页写回其真正的表空间数据文件位置之前,InnoDB会先将脏页的一个副本写入Doublewrite Buffer(位于系统表空间中连续的固定位置)。写Doublewrite Buffer是顺序写。
- 之后才会真正将脏页写入其在表空间数据文件(.ibd)中的最终位置(随机写)。
- 目的: 解决部分页写入问题。如果页在写入最终位置的过程中发生崩溃导致页损坏(只写了16KB的一部分),InnoDB在恢复时,可以从Doublewrite Buffer中找到该页的一个完好副本,并用它来覆盖损坏的页。由于Doublewrite Buffer的写是原子性且顺序的,它能保证副本页的完整性。然后可以安全地使用Redo Log重做该页的变化。为页写入失败提供了一道安全网。
- 重做日志文件(Redo Log Files):
图解层次关系 (从大到小):
+---------------------------+
| Database | # 数据库实例,包含多个表空间
+---------------------------+
|
v
+---------------------------+
| Tablespace(s) | # 逻辑容器: ibdata1, table.ibd, undo_001, ibtmp1
+---------------------------+
|
+--------------------------------------------------------+
| |
v v
+-----------------------+ +--------------------------+ +----------------------+
| Segment (e.g., Index | | Segment (e.g., Undo Seg) | | ... Other Segments ...|
| Leaf Segment) | | | | |
+-----------------------+ +--------------------------+ +----------------------+
| |
v v
+----------------------+ +----------------------+
| Extent |----->| Extent | # 由连续的64个页组成 (1MB for 16K Page)
+----------------------+ +----------------------+
|
v
+----------------------+ +----------------------+
| Page (1) |----->| Page (2) | # 16KB 最小单位
+------------+---------+ +------------+---------+
| |
v (Inside Page Structure) v (Inside Page Structure)
+---------------------------------------------------+
| File Header | Page Header | Infimum | User Records |
| (38B) | (56B) | (13B) | (Rows!) |
| ... Free Space ... | Page Directory | File Trailer|
| | (Slots) | (8B) | |
+---------------------------------------------------+
|
v (Inside User Records)
+---------------------------------------------------+
| Row Format (e.g., Dynamic): |
| - Field Length Offsets |
| - Trx ID & Roll Pointer (for MVCC) |
| - Non-NULL Fixed-Length Columns |
| - Non-NULL Variable-Length Columns |
| - NULLable Columns Bitmap |
| - Overflow Page Pointers for large columns |
+---------------------------------------------------+
总结关键点:
-
表空间为中心: 所有数据最终存储在表空间文件(.ibd, ibdata1, undo_001, ibtmp1)。
-
页是最小单位: 所有的磁盘I/O(读写、刷盘、缓存)都以16KB的页为单位进行。
-
索引组织数据: 表数据按主键顺序(聚簇索引)物理存储。二级索引存储索引列+主键值。
-
页内结构: 数据页有严谨的结构(页头、记录头、行记录、槽目录、页尾),
DYNAMIC行格式优化了大字段存储。 -
段、簇管理: 段(叶子段、非叶子段)是表空间的主要单元,由连续的簇(64个页,1MB)组成,簇再由连续的页组成,利于减少碎片和提高效率。
-
核心机制:
- 重做日志(Redo Log): 保证事务持久性(先写日志)。
- Doublewrite Buffer: 保证页写入的原子性,防止页部分写入损坏。
- Change Buffer: 加速二级索引的DML操作(在非唯一索引上)。
- Undo Log (在Undo表空间): 支持事务回滚和MVCC。
-
分离是现代趋势:
- 使用
innodb_file_per_table=ON将用户表数据分离到独立的.ibd文件。 - 使用Undo表空间单独管理Undo日志。
- 临时表空间管理临时数据。
- 使用
理解这些磁盘结构,有助于你更深入地理解InnoDB的运作原理,从而在优化查询(例如设计高效的索引避免回表)、配置存储参数、分析性能瓶颈和进行故障排查时做出更明智的决策。
浙公网安备 33010602011771号