InnoDB页结构深入:页分裂、页合并、填充因子——B+树底层机制全解析

大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!

InnoDB以页为单位管理数据,默认16KB一页。数据行存储在页里,页与页之间通过B+树组织。

但很多人不知道的是:页不是永远"整整齐齐"的。 新数据插入时,页会分裂;数据删除时,页会合并。这些底层操作直接决定了写入性能、空间利用率和查询效率。

今天把页结构这件事彻底拆开讲清楚。

一、先理清一个概念:页到底是什么

数据页(Data Page) 是InnoDB存储数据的基本单位,默认16KB。一个页里存放多行记录,行与行之间通过单向链表连接。

每个数据页有固定的结构:

组成部分 大小 作用
File Header 38字节 页的通用信息(页号、页类型、上下页指针)
Page Header 56字节 页的状态信息(记录数、空闲空间、最大事务ID)
Infimum + Supremum 26字节 虚拟记录,标记页内记录的下界和上界
User Records 不定 实际存储的行记录
Free Space 不定 未使用的空间
Page Directory 不定 页内记录的稀疏索引,加速页内查找
File Trailer 8字节 校验页的完整性

关键点 :页的File Header中有两个指针------FIL_PAGE_PREV和FIL_PAGE_NEXT,指向同一层的相邻页。所有数据页通过这两个指针串成一个双向链表。

二、页分裂:顺序插入 vs 随机插入

页分裂是InnoDB在页空间不足时,把一页拆成两页的过程。

什么情况下触发页分裂?

当一个页的Free Space不足以容纳新插入的记录时,InnoDB会执行页分裂:

  1. 分配一个新的数据页

  2. 把原页中的一部分记录移动到新页

  3. 调整B+树的指针

顺序插入 vs 随机插入------性能差异巨大

顺序插入(自增主键)

如果主键是自增的,新记录总是插入到B+树最右侧的页。这个页通常还有Free Space,插入操作直接写入即可,不会触发频繁的页分裂。

实测数据(100万行顺序插入):页分裂次数约200次 ,插入耗时约12秒。

随机插入(UUID/雪花ID)

如果主键是随机的(UUID、雪花ID等),新记录可能插入到B+树的任意位置。如果目标页刚好满了,就需要分裂。

实测数据(100万行随机插入):页分裂次数约8000次 ,插入耗时约45秒。

差异原因:顺序插入的页分裂集中在B+树最右侧,分裂频率低;随机插入的页分裂分散在B+树的各个位置,分裂频率高。每次页分裂都需要分配新页、移动记录、调整指针,开销远高于简单的插入操作。

一个容易被忽略的细节 :即使主键是自增的,如果innodb_fill_factor设置不当,也可能导致空间浪费和额外的页分裂。

三、页合并:删除数据后发生了什么

页合并是页分裂的逆过程------当相邻的两个页的记录总大小可以合并到一个页时,InnoDB会执行页合并。

触发条件 :当页中删除的记录超过MERGE_THRESHOLD(默认50%)时,InnoDB会检查相邻页是否可以合并。

页合并的代价:

  • 需要移动记录

  • 需要调整B+树指针

  • 可能影响并发性能

生产环境的建议 :不要依赖页合并来回收空间。 如果业务中有大量删除操作,考虑用OPTIMIZE TABLE或ALTER TABLE ... ENGINE=InnoDB重建表,一次性回收碎片空间。

四、填充因子:控制页的"预留空间"

innodb_fill_factor是InnoDB的一个参数,控制页中预留多少空间给未来的插入。

默认值:100(即页完全填满才分裂)。

设置建议:

  • 顺序插入场景(自增主键):保持100。因为新数据总是插在最右侧,不需要预留空间。

  • 随机插入场景 (UUID/雪花ID):设置为80-90。预留10%-20%的空间给未来的插入,降低页分裂频率。

  • 频繁更新的表:适当降低填充因子(如70-80),为更新操作预留空间。

注意 :innodb_fill_factor只对新建的表或重建的表生效,不会改变现有表的页结构。

五、页分裂对查询性能的影响

页分裂不仅影响写入性能,也会影响查询性能。

原因:页分裂后,原本连续存储的记录可能被分散到两个页中。查询时需要读取更多的页,I/O次数增加。

一个真实案例:某订单表用UUID做主键,运行半年后查询性能从50ms下降到300ms。检查发现B+树的页分裂严重,索引碎片率超过40%。重建表后,查询性能恢复到60ms。

六、实战:监控页分裂与空间碎片

监控页分裂次数:

bash 复制代码
SHOW GLOBAL STATUS LIKE 'Innodb_pages_created';
SHOW GLOBAL STATUS LIKE 'Innodb_pages_written';

Innodb_pages_created持续增长,说明页分裂在频繁发生。

检查表空间碎片:

bash 复制代码
SELECT 
    TABLE_NAME,
    DATA_LENGTH,
    INDEX_LENGTH,
    DATA_FREE
FROM information_schema.TABLES 
WHERE TABLE_SCHEMA = 'your_db';

DATA_FREE表示表中已分配但未使用的空间。如果DATA_FREE远大于DATA_LENGTH,说明碎片严重。

重建表回收空间:

bash 复制代码
ALTER TABLE your_table ENGINE=InnoDB;

七、小结

InnoDB的页结构是B+树底层机制的核心。页分裂的触发条件、顺序插入与随机插入的性能差异、填充因子的控制作用------这些底层细节直接影响写入性能和空间利用率。选主键时,自增主键之所以优于UUID,根本原因就在于页分裂的频率。理解页结构,才能理解InnoDB为什么这样设计。

小耶在手,SQL 不愁

还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽......我们下次见~

相关推荐
用户83145509803115 小时前
如一 Agent 架构解读(二):上下文工程——为模型构造它唯一的现实
人工智能·架构
l1t15 小时前
DeepSeek总结的PostgreSQL因选择而宽松,因偶然而永久
数据库·postgresql
Nturmoils15 小时前
数据同步中断后,KFS 怎么把链路接回来
数据库
怕浪猫15 小时前
Agent 工程化面试:从开发到部署的 5 个关键问题
面试·架构·github
行者全栈架构师15 小时前
【鸿蒙心迹】鸿蒙网络请求架构实战——@ohos.net.http 到 Axios 封装、拦截器与统一错误处理(HarmonyOS 7.x)
前端·算法·架构
leisoo809715 小时前
股票筹码分布怎么用获利比例成本区间与集中度实战 IG50免费开源股票数据API接口
开发语言·jvm·数据库·python·开源
字节跳动的猫16 小时前
LikeShop 商品评价体系二开:追评、晒图审核与评价标签筛选功能开发
运维·数据结构·数据库
m0_5873830016 小时前
工业场景设备维修维护实战技巧 全流程标准化落地与常见问题排查指南
java·spring·小程序·架构·需求分析
行者全栈架构师16 小时前
从 55% 到 6%:一个快餐营养规划器的算法迭代实录
后端·算法·架构
ZGIAI16 小时前
Agent 重试会不会越帮越乱?
人工智能·架构