土法炼钢 · 系统与基础设施

【MySQL InnoDB 内核】页结构与行格式

文章导航

分类入口
databasekernel
标签入口
#mysql#innodb#page#row-format#fil-header#rem0rec

目录

页结构与行格式

InnoDB 把数据和索引都组织在固定大小(默认 16KB)的页面里。理解页内布局——FIL 头、记录目录、Infimum/Supremum 哨兵、聚簇索引即数据——是读 EXPLAIN、分析页分裂、理解 VARCHAR 溢出存储的前提。本文对照 PostgreSQL 页面布局 的 8KB Page + HeapTupleHeaderData,说明 InnoDB 的设计分叉。

源码:MySQL 8.0.36storage/innobase/include/fil0fil.hrem0rec.hpage0page.cc


一、InnoDB 页的逻辑结构

1.1 一页里有什么

flowchart TB
  subgraph page [16KB InnoDB Index Page]
    FIL[FIL Header 38B]
    IDX[Index Header]
    INF[Infimum Record]
    REC[User Records 链表]
    SUP[Supremum Record]
    FREE[Free Space]
    DIR[Page Directory Slots]
    TRA[FIL Trailer 8B]
  end
  FIL --> IDX --> INF --> REC --> SUP --> FREE --> DIR --> TRA
区域 作用
FIL Header 页号、表空间 ID、LSN、页类型 checksum
Index Header 索引层信息:记录数、方向、层级、根标志
Infimum / Supremum 虚拟最小/最大记录,User Records 挂在其间
User Records 实际行数据,按主键顺序单向链表
Page Directory 稀疏槽位,二分查找入口
FIL Trailer 末尾 checksum,与 header 呼应

PG 用 linp 行指针数组 + ItemId 管理变长 tuple;InnoDB 用 单向链表 + Page Directory——插入时可能重排 slot,但链表顺序保持键序。

1.2 页类型

类型 常量 用途
FIL_PAGE_INDEX 索引页 聚簇/二级 B+Tree 节点
FIL_PAGE_UNDO_LOG Undo 页 事务回滚段
FIL_PAGE_INODE Inode 页 段管理
FIL_PAGE_IBUF_FREE_LIST Change buffer 延迟二级索引
FIL_PAGE_TYPE_ALLOCATED 新分配 尚未格式化

表空间文件(.ibd)由大量 FIL_PAGE_INDEX 组成 B+Tree。


二、FIL Header 关键字段

fil0fil.h 定义页头(38 字节,简化):

偏移 字段 含义
0 FIL_PAGE_SPACE_OR_CHKSUM 校验和 / 表空间
4 FIL_PAGE_OFFSET 页在表空间内偏移
8 FIL_PAGE_PREV / NEXT B+Tree 兄弟页指针
24 FIL_PAGE_LSN 页最后修改 LSN
34 FIL_PAGE_TYPE 页类型

FIL_PAGE_LSN 与 redo log 对应——崩溃恢复时判断页是否需要 redo apply。PG 页头有 pd_lsnPageHeaderData),语义类似。


三、行记录格式(ROW_FORMAT)

3.1 隐藏列

每行用户数据外,InnoDB 自动附加:

作用
DB_TRX_ID 最后修改该行的事务 ID
DB_ROLL_PTR 指向 undo 记录,MVCC 回滚链
DB_ROW_ID 无显式主键时 InnoDB 生成的 6 字节 row id

PG 用 xmin/xmax/ctid 存在 HeapTupleHeaderData;InnoDB 用 undo 链 + trx id——这是 MVCC 方案的根本分叉(第 7 章)。

3.2 COMPACT / DYNAMIC(8.0 默认 DYNAMIC)

8.0 默认 ROW_FORMAT=DYNAMIC(与 COMPACT 记录头相同,溢出策略不同):

记录头(5 字节变长) 包含:

变长列长度列表 + NULL 位图 + 列数据 紧随其后。

[变长长度列表][NULL 位图][记录头 5B][trx_id][roll_ptr][列1][列2]...

源码:rem0rec.hrec_get_offsets() 解析记录。

3.3 溢出列(OFF-page)

当变长列超过页内阈值(约 768 字节 半页规则,视行大小与格式而定),DYNAMIC/COMPACT 将大列存到 溢出页(linked list of pages),聚簇索引叶只留 20 字节前缀。

PG 用 TOAST 外存(第 02 章 PG);InnoDB 用 overflow page 链——SELECT 大字段可能触发随机 IO。


四、聚簇索引与二级索引

4.1 聚簇索引 = 表数据

InnoDB 必须有聚簇索引:

叶节点页存完整行(除溢出部分);非叶节点存 主键 + 子页指针

4.2 二级索引

二级索引叶节点存:

(二级索引键列..., 主键列...)

回表(lookup clustered index)= 用主键再查聚簇索引。覆盖索引Using index)= 所需列全在二级索引中,无需回表。

flowchart LR
  SEC[Secondary Index Leaf] -->|主键| CLU[Clustered Index Leaf]
  CLU --> ROW[完整行]

PG 的 Index-Only Scan 需要 visibility map;InnoDB 二级索引叶 不含 trx id 可见性信息——可能需要回表判断可见性(第 7 章)。

4.3 页分裂

插入导致叶页满 → 分裂为两页,父节点可能级联分裂。分裂时 LSN 更新、redo 记录结构变更——高并发插入顺序主键(自增) vs 随机主键(UUID)的页分裂模式是生产调优经典话题(第 11 章)。


五、Page Directory 与查找

页内记录 单向链表 按键序排列,但链表扫描慢。Page Directory 在页尾存 稀疏 slot(通常每 4–8 条记录一个 slot),查找时:

  1. 二分 directory slot 定位区间
  2. 在区间内沿链表扫描

B+Tree 层:从根节点向下,每层一次二分 + 页内查找。与 PG B-Tree 的 /_bt_search 类似,但页内布局不同(PG B-Tree 第 14 章)。


六、实验:查看表空间与页大小

需 MySQL 8.0 实例(本环境未安装——请本地验证)。

6.1 表空间元数据

SELECT space, name, space_type, file_size, fs_block_size
FROM information_schema.INNODB_TABLESPACES
WHERE name LIKE '%your_table%';

file_size 以页为单位增长;默认页大小 innodb_page_size=16384

6.2 行格式

SHOW TABLE STATUS LIKE 'your_table'\G
-- Row_format: Dynamic
SELECT * FROM information_schema.INNODB_TABLES
WHERE name = 'your_db/your_table';

6.3 十六进制页 dump(高级)

社区工具 innodb_ruby 或 debug 编译 MySQL 可 dump 单页。生产环境只读副本上操作:

# 示意:从 .ibd 提取 page 0(需确认 offset 与页号)
xxd -s $((page_no * 16384)) -l 16384 table.ibd | head -20

页头前 4 字节常为 checksum;偏移 24 附近可见 LSN 片段。勿在生产可写实例上随意 dd 正在写入的文件——应用快照或 offline copy。


七、PG 对照小结

维度 PostgreSQL InnoDB
默认页大小 8KB 16KB
行头 HeapTupleHeaderData (xmin/xmax/ctid) 5B 记录头 + trx_id/roll_ptr
MVCC 存储 多版本行内 undo 链外置
大字段 TOAST 溢出页
聚簇 堆表无强制聚簇 聚簇索引即表
页内查找 ItemId + line pointer 链表 + Page Directory

八、工程坑点

无显式主键。隐式 DB_ROW_ID 聚簇 + 二级索引都变大——所有二级索引叶多 6 字节 row id,且插入分布可能更随机。

UUID 主键。随机插入导致页分裂频繁、Buffer Pool 污染——顺序 ID 或 UUID_TO_BIN 有序化是常见方案。

ROW_FORMAT=REDUNDANT。老格式仍占用更多空间——8.0 应使用 DYNAMIC。

忽略溢出列 IOSELECT * 大 TEXT 列可能每行多几次随机读——应用层只选需要的列。


九、边界


上一篇InnoDB 架构与线程模型

下一篇:Buffer Pool 与 LRU(第 03 篇,规划中)

参考资料

  1. MySQL 8.0 Reference Manual, InnoDB Row Formats
  2. MySQL 8.0 Source, storage/innobase/include/rem0rec.h, fil0fil.h
  3. MySQL 8.0 Source, storage/innobase/page/page0page.cc
  4. 《MySQL 技术内幕:InnoDB 存储引擎》第 4–5 章(对照 8.0 标注过时处)

读完这篇,下一步读什么

优先读同系列或同问题的下一篇,把单篇消费变成主题集群。


By .