目录
前言
页分裂:数据页正常被写满时,B+树被“拆分”,部分数据被移到新页。
页分裂主要出现在数据乱序插入时,需要插入到数据页的中间位置,并且数据页已经没有办法存下插入的数据(页的大小是16K,并没有很大),就需要考虑页分裂,拉取一个新的空白页,并将新纪录和原数据页的部分内容迁移。
影响:带来写入放缓、空间变低、碎片增加,顺序插入能极大降低页分裂。
关于mysql的介绍,可参考:
聊聊对Mysql数据库的见解_如何更好的理解mysql-CSDN博客文章浏览阅读1.5k次,点赞34次,收藏27次。Structure Query Language(结构化查询语言)简称SQL。而mysql属于关系型数据库。MySQL最新版8.0.21安装配置教程~-CSDN博客以上为数据库在服务中的使用。_如何更好的理解mysqlhttps://blog.csdn.net/weixin_50055999/article/details/147686993?spm=1011.2415.3001.5331 页的大小是固定的(16K),如果有一条记录非常大(如text类型),则一个页无法存储这条记录,需要借助溢出页。
1、MySQL页的介绍
MySQL数据表以文件方式存放在磁盘中,默认使用共享表空间(0)存储。
共享表对于Innodb来说:
当使用共享表空间时,所有表的数据和索引会存储在一个共享的 ibdata 文件中。表结构以
.
frm 文件的形式存储在与表对应的文件夹中。
如下:
1.1、页的定义
页为mysql的innodb引擎的存储磁盘的最小单元,每个页默认为16kb。
64个连续的数据页称为一个extent(区),64个页组成一个区,所以区的大小为1MB(16*64=1024),连续的256个数据区称为一组数据区。
关于页的结构如下所示:
页加载进内存后才会通过扫描页来获取行记录比如查询 id=1,是获取 1所在的数据页,加载进内存后取出1这一行。
1.2、页分裂的介绍
当一页数据(InnoDB数据页)已经存满,再插入一行数据时,原有页空间不足,系统会将该页“一分为二”,将部分数据挪到新页,以腾出空间,实现数据均匀分布。
如下图所示:
1.3、页分裂场景
通常发生在B+树节点(无论主索引、二级索引):页满时新数据的插入。
页分裂主要出现在数据乱序插入时,需要插入到数据页的中间位置,并且数据页已经没有办法存下插入的数据(页的大小是16K,并没有很大),就需要考虑页分裂,拉取一个新的空白页,并将新纪录和原数据页的部分内容迁移。
- 向B+树索引页中间插入新值(不是顺序插入,而是散插)
- 页高效利用空间:数据“尽量均匀”分布
- 当有新数据插入中间,导致页空间不足,即“分裂”,把一半数据移到新页
- 无自增主键或有二级索引的表,频繁随机插入数据,很容易造成页分裂。
2、页分裂执行流程
2.1、页分裂流程
1、插入前的检查
当一个插入操作被发起,首先,InnoDB会定位到要插入记录的位置。这通常涉及到B+树索引的搜索。
在找到插入点后,InnoDB检查目标页是否有足够空间容纳新记录。这一步是通过比较页内剩余空间和新记录大小来完成的。
2、判断是否需要页分裂
如果页内有足够空间,则直接插入记录,无需页分裂。如果没有足够空间,InnoDB决定进行页分裂。
3、执行页分裂
创建新页:系统分配一个空白页,这个页的数据结构与被分裂的页相同。
记录的迁移和分配:选择一部分记录从原页迁移到新页。InnoDB试图平均分配两个页的空间利用,同时保持B+树的顺序性不变。
插入新记录:根据新记录的键值决定它是插入到原页还是新页中。
调整B+树索引:页分裂可能导致父节点的键值需要更新,以反映新页的加入。如果父节点没有足够空间,这个过程可能递归到更高层次的节点,甚至到根节点,可能导致树的高度增加。
4、更新系统元数据和日志
元数据更新:包括页的元数据,如页链表、空闲列表等。
写入重做日志(Redo Log):每次页分裂操作都会记录在重做日志中,确保在系统故障时可以恢复数据。
总结:
- 当页已满时,再插入一条“本页本应存放位置”数据
- InnoDB自动“分裂”出一个新页,把原页约一半记录移动到新页,插入新数据,然后B+树父节点做相关key调整。
2.2、页分裂更新流程
如下图所示:
可以发现后面数据页里的主键值比前一个数据页的主键值小, 里面的数据就会进行数据挪动。
如下图所示:
通过页分裂,我们只要将主键为2的数据行与主键值为4的数据行互相挪动一下位置,就可以保证后面一个数据页的主键值比前一个数据页中的主键值大了。
2.3、插入流程
步骤分为:
1、检查空间:
当InnoDB尝试插入新的数据时,它首先会检查当前数据页是否有足够的空间来容纳新数据。
2、分裂决策:
如果当前页没有足够的空间,InnoDB就会决定进行页分裂。它会创建一个新的数据页,并将原数据页中的一部分数据(通常是中位数附近的数据)移动到新页中,以确保新插入的数据可以放在合适的位置。
3、数据移动:
实际的数据移动过程涉及将原数据页中的一部分行复制到新页中,并更新相关的索引和指针以反映这种变化。这可能涉及到多个数据页的调整,以确保数据的连续性和索引的正确性。
4、更新链接:
InnoDB会更新数据页之间的双向链表指针,以确保分裂后的数据页仍然按照正确的顺序链接在一起。同时,它也会更新索引结构以反映新数据页的存在和位置。
5、插入新数据:
一旦页分裂完成,InnoDB就可以在新的位置插入新数据了。这通常是在分裂后留下的空间中进行的。
⚠️注意:
页分裂不仅发生在插入操作中,当更新操作导致行的大小增加,使得当前页无法容纳时,也可能发生页分裂。同样地,删除操作可能导致页的合并,以释放空间并提高存储效率。
3、页分裂的影响
3.1、效率降低
产生页分裂瞬间,需要做大量物理数据搬移,效率降低(插入顿卡)。后续查询扫描更多的页,性能下降。
3.2、利用空间率降低
对磁盘空间利用率变低,分裂后,两个页面的填充往往都不是100%,有大量空间闲置。如果后续有大量删除,出现“碎片”空洞,导致读取性能下降,空间浪费。
3.3、维护成本增加
加重B+树的维护成本:频繁分裂使树高度增加,维护(OPTIMIZE、收缩空间、defragmentation)负担重。
4、和顺序插入的对比
4.1、顺序插入
自增主键/自增字段:
新数据永远追加到B+树最右侧叶子节点。
一般等最后一个页满了才分裂(很稳定且规律)。
页分裂次数低,空间利用率高。
4.2、随机插入
UUID主键或二级索引/非顺序整数主键:
任何插入都可能落到“中间页面”,很容易页分裂,碎片多。
5、如何避免页分裂
- 优先用自增或有序的主键(bigint auto_increment),让写入顺序增长。
- 如果只能用UUID,调整innodb_fill_factor/合理设计索引,或考虑分区。
- 定期“OPTIMIZE TABLE”整理碎片。
- 批量导入时排序后再写,降低插入中间导致分裂概率。
- 调整业务,如果真的插入都是随机主键,关注业务表膨胀/性能问题。
扩展
- 页合并(Page Merge): 如果页内记录少于某个比例,系统自动或手动会将稀疏页与相邻页合并,收缩碎片空间。
- B+树分叉、重平衡: 分裂后父节点也可能因满而递归分裂。
参考文章: