前言
页分裂(Page Split)是 InnoDB 存储引擎中一个极其重要却常被忽视的底层机制。它直接影响着数据库的写入性能、空间利用率和查询效率,是每一位研发人员和 DBA 都应该深入理解的核心知识。
本系列文章将分为上、中、下三篇,由浅入深地带你全面掌握页分裂:
话不多说,我们从上篇开始。
一、什么是页分裂?
先聊聊"页"
InnoDB 中,页(Page)是数据存储和读写的最小单位,默认大小为16KB。而 MySQL 的表是索引组织表,每个页中的记录按照主键的物理顺序排列(并非插入顺序),形成一个单向链表。页与页之间则通过双向链表相互连接,共同构成一棵B+ 树索引结构。
再说"页分裂"
当我们向一个已经塞满了的页中插入新数据时,这个页已经没有足够的空间来容纳新记录了。
此时,InnoDB 会执行一个操作 ——页分裂(Page Split)。
页分裂,就是将一个已满的数据页拆分成两个页,并将部分数据迁移到新页中,以腾出空间存放新插入的记录。
这个过程对应用层完全透明,但在底层却涉及数据搬迁、指针调整、B+ 树结构变更等一系列操作。
二、页分裂的过程
页分裂的触发和执行可以分为以下几个步骤:
① 插入请求
当一条insert语句执行时,InnoDB 根据主键值定位到数据应该插入的目标页。
② 判断空间是否充足
InnoDB 检查目标页的剩余空间。如果空间足够,直接插入,流程结束。如果空间不足,则触发页分裂。
③ 申请新页、数据迁移
InnoDB 从表空间中申请一个新的空白页。
这是页分裂的核心步骤。InnoDB 会将原页中大约一半的记录移动到新页中。具体来说:
原页保留前半部分的记录
新页承接后半部分的记录
新插入的数据根据主键大小,放入对应的页中
这里的"一半"并不是严格的 50%,InnoDB 会根据实际情况做一定的优化调整。
④ 调整指针与索引
数据搬迁完成后,还需要做以下工作:
- 更新页之间的双向链表指针
将新页正确地链接到原页和原页的下一页之间
- 更新父节点(上层索引页)
在 B+ 树的上层节点中,插入一条指向新页的索引记录
如果上层节点也满了,则会递归触发上层页的分裂,极端情况下甚至可能导致 B+ 树层级增加
三、页分裂的弊端
页分裂虽然是 InnoDB 维护 B+ 树结构的必要机制,但它带来的负面影响不容忽视。
1 性能开销大
页分裂涉及数据复制、页申请、指针修改、索引更新等多个步骤,这些操作都需要额外的 CPU 和 I/O 资源。
在高并发写入场景下,频繁的页分裂会显著拖慢写入性能。
2 空间利用率下降
分裂后,原页和新页通常都只有一半左右的数据,这意味着页的空间利用率从接近 100% 骤降到约50%。
大量的页分裂会导致表的物理存储膨胀,占用更多的磁盘空间,同时也会降低缓冲池的命中率。
3 产生磁盘碎片
页分裂后,新申请的页在磁盘上的物理位置不一定与原页相邻。
这会导致原本连续的数据变得离散分布,使得范围查询和全表扫描时需要更多的随机 I/O,而非高效的顺序 I/O。
4 影响并发性能
页分裂过程中,InnoDB 需要对相关的页加锁。这意味着在分裂期间,其他事务对这些页的读写操作可能被阻塞,从而影响并发吞吐量。