MySQL 的段区页行
我们来详细拆解一下 MySQL InnoDB 存储引擎中段(Segment)、区(Extent)、页(Page) 和行(Row) 的概念及其关系。它们是 InnoDB 管理磁盘空间和数据的核心逻辑单元,层级自底向上依次是:行 → 页 → 区 → 段。
你可以把它们想象成书本的组织结构:
行 (Row):书中的每一行文字。
页 (Page):书中的一页纸,包含多行文字。
区 (Extent):一叠连续的、固定数量的纸张(页)。
段 (Segment):一个大的章节,由多个“叠纸”(区)组成,专门存放特定类型的内容(如正文、索引)。
1. 行 (Row)
定义: 数据存储的基本单位,对应数据库表中的一条记录。
大小: 可变,取决于表中定义的列及其存储的数据。一行记录包含所有列的值(可能包含隐藏列如事务ID、回滚指针)以及行头信息(如删除标记、下一行指针等)。
存储位置: 存储在页的内部。
关键点:
InnoDB 支持不同的行格式 (
COMPACT
,DYNAMIC
,COMPRESSED
,REDUNDANT
),它们影响如何存储变长列(如 VARCHAR、TEXT、BLOB)和 NULL 值,进而影响存储效率和性能。DYNAMIC
是现代版本的默认格式。对于非常大的变长列(如 TEXT、BLOB),如果一页放不下,
DYNAMIC
和COMPRESSED
格式只会将前 768 字节存储在行内(主页面),剩余部分存储在单独的溢出页中,并通过指针引用。这避免了强制其他行迁移。每行有一个唯一的 Row ID(如果表没有定义主键或唯一非空索引,InnoDB 会自动生成一个隐藏的 6 字节 Row ID 作为主键)。
2. 页 (Page)
定义: InnoDB 磁盘管理的最小单位,也是内存(Buffer Pool)与磁盘交换数据的基本单位。所有读写操作最终都发生在页级别。
大小: 固定,默认为 16KB。可以在初始化 MySQL 实例时通过
innodb_page_size
配置(通常不建议修改)。结构: 一个页包含多个部分:
File Header: 记录页的元信息,如页号、前后页指针(构成双向链表)、页类型(数据页、索引页、Undo页、系统页等)、属于哪个表空间。
Page Header: 记录页内部的状态信息,如槽位信息、记录数、空闲空间起始位置、堆中记录数等。
Infimum + Supremum Records: 两个虚拟的行记录,分别表示页中最小和最大的伪记录,用于界定记录的边界。
User Records: 实际存储行记录的地方。 记录按照主键顺序(或指定的索引顺序)以单向链表的形式存储(物理上不一定连续,但逻辑有序)。每条记录包含其行头信息和列数据。
Free Space: 页中尚未使用的空间。随着记录的插入而减少,随着记录的删除而增加(但删除的空间可能形成碎片)。
Page Directory: 页内记录的稀疏索引(或称为槽 Slots)。它记录了一组指针,指向页中某些特定位置的记录(通常是每隔几条记录取一个)。通过二分查找 Page Directory 可以快速定位到某条记录的大致位置,然后沿着链表顺序查找精确位置。这是页内快速查找的关键。
File Trailer: 主要用于校验页数据的完整性(如 Checksum)。
关键点:
I/O 单位: 磁盘读写总是以整个页为单位进行的。Buffer Pool 管理的就是这些页在内存中的副本(缓存)。
组织方式: 同一索引的页通过 File Header 中的前后页指针连接成双向链表,逻辑上形成一个有序的集合(按索引键排序)。
页内查找: 通过
Page Directory + 行记录链表
实现高效查找(类似跳表思想)。页分裂: 当一个已满的页需要插入新记录且没有足够连续空间时,会发生页分裂:大约一半的记录被移动到新申请的页,新记录插入合适的位置,并调整前后页的指针。这是一个开销较大的操作。
页合并: 当相邻页的删除操作导致其利用率很低时,InnoDB 可能会尝试将它们合并,以释放空闲页。
3. 区 (Extent)
定义: 由连续的 64 个页构成的空间单元。默认页大小 16KB 时,一个区的大小为 1MB (64 * 16KB)。
目的:
减少随机 I/O,提高顺序 I/O: 当为表或索引分配新空间时(如插入大量数据),InnoDB 倾向于一次性分配一个完整的区(连续的 64 个页)。因为物理磁盘上连续的数据读取速度远快于随机位置的数据读取。这对于范围扫描(Range Scans)和全表扫描(Full Table Scans)性能至关重要。
减少系统开销: 按区分配比按单个页分配管理起来更高效,减少了空间管理的元数据开销。
关键点:
区是 InnoDB 空间分配(Allocation) 的基本单位。
一个区中的所有页在物理磁盘文件(.ibd 文件)上是连续存储的(至少是逻辑连续的,现代文件系统和 SSD 可能物理不连续,但文件系统会尽量保证)。
段在初始分配时,会一次性申请一个“碎片页”(Fragments Pages)而不是整区,但后续扩展主要按区分配。
4. 段 (Segment)
定义: 一个逻辑概念,是由多个区组成的集合。它是 InnoDB 中表空间内更高一级的管理结构。
核心关联: 段与 B+树索引节点类型紧密相关:
叶子节点段 (Leaf Node Segment): 存储 B+树索引中叶子节点的数据。对于聚簇索引(Clustered Index),叶子节点段存储的就是完整的表行数据。对于二级索引(Secondary Index),叶子节点段存储的是索引列的值和对应的主键值。
非叶子节点段 (Non-Leaf Node Segment): 存储 B+树索引中非叶子节点(内部节点) 的数据。这些节点存储的是指向下一层级节点的索引键值和页指针(Page Pointer)。
目的:
逻辑分组: 将属于同一个 B+树索引的不同层级(叶子节点和非叶子节点)的数据组织到不同的段中。这样 InnoDB 可以更灵活地为不同类型的节点数据分配空间和管理策略(例如,叶子节点段通常增长更快)。
空间管理: 段负责管理其下属的区和页的分配、回收和使用情况。
关键点:
创建一个表(主键即聚簇索引)会创建至少两个段:一个叶子节点段(存储行数据),一个非叶子节点段(存储聚簇索引的内部节点)。
创建一个二级索引会额外创建两个段:一个该索引的叶子节点段,一个该索引的非叶子节点段。
段本身在磁盘上没有连续存储的要求,它包含的区可以在表空间文件(.ibd)中分散存放。但区内部的页是连续的。
回滚段 (Rollback Segment): 这是一个特殊的段,用于存储 Undo Log 记录。它包含多个 Undo Slot,每个 Slot 对应一个事务的 Undo Log 链。
层级关系:
行 (Row)
→ 存储在 →页 (Page)
→ 多个连续的页组成 →区 (Extent)
→ 多个区(叶子节点区/非叶子节点区)组成 →段 (Segment)
→ 多个段(数据段/索引段/回滚段等)存在于 →表空间 (Tablespace)
。空间分配:
当需要为表或索引新增空间时(如插入数据导致页分裂或新申请空间),InnoDB 首先尝试在相关段中已有的区里找空闲页。
如果没有空闲页,则向表空间申请一个新的区(1MB)加入该段。
初始少量分配可能用“碎片页”,但主要按区分配。
性能影响:
区: 连续的区分配优化了顺序 I/O,对大规模数据加载和扫描性能至关重要。
页: 页大小影响 I/O 吞吐量和内存利用率。页分裂/合并影响写性能。页内组织(行格式、Page Directory)影响点查和范围查效率。
行: 行格式和溢出机制影响存储密度和读取大字段的性能。
段: 逻辑分离叶子节点和非叶子节点,允许不同的空间增长模式和管理策略。
总结:
理解段、区、页、行的概念,对于深入理解 InnoDB 存储原理、进行性能调优(如避免页分裂、优化 I/O)、理解表空间文件增长以及进行故障诊断都非常重要。