大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!
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会执行页分裂:
-
分配一个新的数据页
-
把原页中的一部分记录移动到新页
-
调整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 不愁
还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽......我们下次见~