B+树层高、页分裂合并、explain、排序临时表优化

一、B+ 树高度计算(高频)

1. 核心前提

MySQL InnoDB 一页默认大小 16KB

  • 非叶子节点:只存索引键 + 子页指针,数据极小

  • 叶子节点:存完整索引数据(主键+行数据/二级索引主键)

2. 通用计算公式

B+树节点存储数量 = 页大小 / 单条数据大小

树高度层数 = 非叶子节点层数 + 1(叶子层)

3. 行业标准估算(背诵版)

  • 主键为 BIGINT(8字节)+ 指针6字节,单条非叶子数据约14字节

  • 单页非叶子节点可存:16KB / 14B ≈ 1170 个索引条目

  • 叶子节点一条数据约20字节,单页可存 16KB / 20B ≈ 800 条数据

高度3层B+树存储上限:1170 * 1170 * 800 ≈ 10亿数据

4. 结论

日常业务数据表,B+树高度基本都是 2~3 层,查询磁盘IO仅2-3次,效率极高。


二、页分裂与页合并(InnoDB 核心机制)

1. 页分裂(新增/更新数据触发)

触发条件

InnoDB 数据页填充率达到15/16,页空间不足,无法写入新数据。

为何是 15/16?
  • InnoDB 预留了 1/16 空闲空间 做原地更新、小幅插入。
  • 很多业务是更新原有行、少量追加,不用立刻分裂页;

    留一点富余,能直接在当前页原地写,省去页分裂的 IO 开销。

  • 等到塞满到 15/16,剩余空闲已经不够存放新行、也不够行更新扩容,没空间了,只能强制页分裂。

执行流程

  1. 申请一块新的空白数据页;

  2. 将原页后半部分数据迁移到新页;

  3. 在原页写入新数据;

  4. 更新父节点索引指针,关联新旧两个数据页。

弊端

消耗IO资源、产生索引碎片、降低查询性能。

规避方案

  • 主键使用自增主键,数据顺序写入,几乎不会触发页分裂;

  • 禁止使用 UUID、随机字符串当主键;

  • 大批量插入数据时,有序批量导入。

为何能避免?
  • 自增主键:

    新数据主键永远最大,永远往当前最后一个数据页末尾追加

    只有当最后一页真的塞满 15/16 了,才新开一个页,只是分页,不需要挪动老数据、不需要中间拆分

    → 这叫页扩容,不是页分裂

  • 无序主键(UUID / 雪花随机 / 字符串)

    新主键随机,可能插到现有页中间位置

    插到中间 → 当前页空间不够 → 必须把后半截数据搬走拆分 → 触发真正页分裂,大量挪动数据、产生碎片。

2. 页合并(删除数据触发)

触发条件

删除数据后,相邻两个数据页的填充率均低于 1/2

执行流程

  1. 将两个未满的数据页数据合并到同一个数据页;

  2. 释放空闲的数据页;

  3. 更新上层索引指针。

作用

回收磁盘空间、减少索引页数、提升索引查询效率。


三、explain 全字段核心详解(高频)

重点吃透:type、key、rows、Extra 四大核心字段

1. Type(查询类型,优先级从差到优)

  • ALL:全表扫描,最差,无可用索引,必须优化

  • index:全索引扫描,遍历整个索引树,仅优于全表扫描

  • range:范围查询(in、between、>、<、like 前缀匹配),基础合格

  • ref:非唯一索引等值匹配,日常最常用、性能优秀

  • eq_ref:唯一索引/主键等值匹配,每条数据一一对应,性能极佳

  • const:常量匹配,主键/唯一索引等值查询,系统最优级别

  • system:系统表查询,最高级别,业务几乎遇不到

业务标准:日常 SQL 至少保证 range/ref,核心接口必须 eq_ref/const,禁止 ALL/index

2. Key(本次查询真正使用的索引)

  • NULL:未使用任何索引

  • 索引名:本次查询命中的有效索引

  • 注意:possible_key 是可选索引,key 是实际生效索引,以 key 字段为准

3. Rows(扫描行数)

MySQL 优化器预估需要扫描的数据行数,数值越小性能越好

核心意义:直接反映索引筛选能力,rows 过高代表索引筛选度差,存在优化空间。

4. Extra(额外信息,优化核心依据)

  • Using index:覆盖索引,无需回表,性能优秀(最优标识)

  • Using where:服务层过滤数据,索引未完全筛选数据

  • Using filesort:文件排序,严重性能问题,需要优化

  • Using temporary:创建临时表,严重性能问题,需要优化

  • Using join buffer:使用连接缓冲区,关联查询无索引


四、Using Filesort / Using Temporary 成因 + 全套优化方案

1. Using Filesort(文件排序)

成因

排序字段没有建立索引,MySQL 无法使用索引排序,只能在内存/磁盘中手动排序。

出现场景:单独 order by、多字段排序、排序字段非索引字段、索引失效后的排序

优化方案

  1. 给 order by 字段建立单列索引

  2. 多字段排序建立联合索引,严格遵循 where 条件在前、order by 在后 的最左前缀原则;

  3. 避免 order by 字段参与函数运算、隐式类型转换,防止索引失效;

  4. 减少排序数据量,优先通过 where 条件过滤大量数据。

2. Using Temporary(临时表)

成因

分组、去重、关联查询无法通过索引完成,MySQL 需要创建临时表存储中间数据。

高频出现场景:group by、distinct、多表join、聚合查询

优化方案

  1. where + group by + order by 建立联合索引,让筛选、分组、排序全程走索引;

  2. 避免对大字段、不确定字段进行 distinct 去重;

  3. 优先过滤数据,缩小分组范围,先 where 后 group by;

  4. 拆分复杂SQL,避免单条SQL多聚合、多关联导致临时表生成;

  5. 禁止在 group by、distinct 字段上使用函数。

3. 关键总结(重点)

Using filesort、Using temporary 属于硬性性能缺陷,生产环境绝对不允许出现

核心优化思路:让筛选、分组、排序全部命中索引,避免MySQL进行额外的文件排序和临时表创建。