在 MySQL 的几大存储引擎中,InnoDB 凭借事务支持、行级锁以及崩溃恢复等特性成为默认并最常用的引擎。要真正理解 InnoDB 的性能特性及其行为(如锁、索引、磁盘 IO、事务隔离),必须先搞清楚其 逻辑存储结构。
本文从最顶层的表空间,到最底层的记录行,分层介绍 InnoDB 的逻辑结构如何设计、各层的作用是什么、能帮助你理解哪些数据库现象。
1. InnoDB 的逻辑结构总览
InnoDB 按照"由大到小"可以分为四层:
-
表空间(Tablespace)
-
段(Segment)
-
区(Extent)
-
页(Page)
-
行(Row)
结构示意如下:
Tablespace
├── Segment(数据段/索引段/回滚段)
├── Extent(64 个页组成)
├── Page(16KB 默认)
└── Row(记录)
这些层次并非孤立存在,而是为了解决磁盘管理、索引存储、并发访问等问题而设计的。
2. 表空间(Tablespace)
表空间是 InnoDB 逻辑存储结构的最顶层概念。常见的几类表空间包括:
-
系统表空间(system tablespace)
-
独立表空间(file-per-table tablespace)
-
临时表空间(temporary tablespace)
-
Undo 表空间(undo tablespace)
从 MySQL 5.6 开始,一般都推荐使用 "独立表空间 ":每个表独立一个 .ibd 文件,便于管理和迁移。
表空间内部存放各种段(Segment),段继续存放区(Extent),区再存放页(Page)。
3. 段(Segment)
段是 InnoDB 中"对象级别"的存储单位,常见的段包括:
-
数据段(Data Segment) → 存放 B+Tree 的叶子节点页
-
索引段(Index Segment) → 存放 B+Tree 的非叶节点页
-
回滚段(Rollback Segment) → 用来存储 Undo Log
一个表的主键索引是一个聚簇索引(Clustered Index),其叶子节点存储整行数据,因此一个聚簇索引至少包括:
-
一个数据段(叶子节点)
-
一个索引段(非叶子节点)
二级索引(Secondary Index)也有同样的段结构,只是叶子节点存放的是索引键 + 主键。
段的特点是:动态申请区(Extent) 来扩容,以 64 页为单位增长,保证磁盘分配的连续性与性能。
4. 区(Extent)
区是 InnoDB 空间分配的基本单位,每个区包含:
64 个页(Page)
默认页大小为 16KB,因此一个区大小为:
64 * 16KB = 1MB
区的类型:
-
空闲区(FREE)
-
部分使用区(FREE_FRAG)
-
全满区(FULL)
-
用于段(Segment)的区(ALLOCATED)
区的存在意义是:
-
避免频繁的小文件碎片化
-
提升顺序 IO 性能(B+Tree 叶子页被尽量按顺序放在同一或连续区中)
-
方便快速扩表
5. 页(Page)
InnoDB 的核心单位是 页(Page)。这是存储的最小分配单位。
常见的页类型:
| PageType | 作用 |
|---|---|
| B-tree Node (0x45BF) | B+Tree 索引页(最重要) |
| Undo Log Page | Undo 日志 |
| INODE Page | 段的元数据 |
| Index Page | 叶子节点和非叶节点页 |
| Data Page | 行数据存储区 |
| FSM Page | 空闲空间管理 |
默认页大小为 16KB,可通过 innodb_page_size 调整(4KB / 8KB / 16KB)。
页内部结构(以索引页为例):
+-------------------------------+
| File Header (38 bytes) |
+-------------------------------+
| Page Header (56 bytes) |
+-------------------------------+
| Infimum/Supremum 虚拟行 |
+-------------------------------+
| 用户记录(Row) |
+-------------------------------+
| Free Space |
+-------------------------------+
| Page Directory |
+-------------------------------+
| File Trailer (8 bytes) |
+-------------------------------+
其中"Infimum/Supremum" 是页内 B+Tree 的边界记录。
6. 行(Row)结构
一条行记录的真实存储方式与我们在 SQL 中看到的不同。为了支持 MVCC、锁、索引等功能,InnoDB 会对行做特殊处理。
一行存储包括:
-
真实列数据
-
隐藏列
-
DB_TRX_ID(6 bytes):最近修改该行的事务 ID -
DB_ROLL_PTR(7 bytes):指向 Undo Log -
DB_ROW_ID(如果没有主键才自动生成)
-
行格式主要包括:
-
Compact(最常用)
-
Redundant(老格式)
-
Dynamic(TEXT/BLOB 溢出存储)
-
Compressed(页压缩)
如果行中包含大型字段(如 TEXT、BLOB),可能会被分拆到:
-
主记录页存储 20 字节指针
-
数据实际存放在溢出页(Overflow Page)
这也是查询大字段性能差的原因之一。
7. 小结:InnoDB 的逻辑结构如何影响性能?
理解这些结构可以帮助你分析实际问题。
例如:
-
为什么查询主键比二级索引快?
→ 因为聚簇索引叶子节点就存整行,二级索引需要回表。
-
为什么大表插入时可能出现性能跳变?
→ 因为段要扩容,申请新的区。
-
为什么页分裂会导致性能下降?
→ 页被写满后需要拆裂,B+Tree 维护成本变高。
-
为什么 delete 后表文件不缩小?
→ 页被标记为 Free,但不会自动回收空间,仍属于表空间。
结语
InnoDB 的逻辑结构是理解 MySQL 底层行为的关键。
从表空间、段、区、页再到具体的记录行,每一层结构都有其强烈的设计目的:提高索引效率、减少碎片、增强磁盘顺序访问、实现 MVCC 与事务隔离。
理解这些结构,不仅能帮助你更好地调优 SQL,还能在面对复杂问题(锁等待、慢查询、空间膨胀)时做出更准确的判断。