一、先明确核心前提
MySQL 的 InnoDB 存储引擎中,B + 树的每个节点对应一个数据页(默认大小 16KB=16384 字节),我们需要先确定:
- 非叶子节点:只存储「索引键 + 子节点指针」,不存数据;
- 叶子节点:存储「索引键 + 数据行指针 / 整行数据」(主键索引叶子节点存整行,二级索引存主键);
- 指针大小:InnoDB 中指针固定为6 字节;
- 索引键大小:以最常见的
BIGINT主键(8 字节)为例(如果是 INT 则 4 字节,可替换计算)。
二、分步计算(以 BIGINT 主键为例)
1. 计算非叶子节点的子节点数(扇出数)
非叶子节点的每个条目 = 索引键(8 字节) + 子节点指针(6 字节) = 14 字节。
一个 16KB 的页,扣除页头 / 页尾的固定开销(约 100 字节),可用空间约 16284 字节。
单页可存储的条目数 = 16284 ÷ 14 ≈ 1163(向下取整,保证不超页大小)。
即:每个非叶子节点最多指向 1163 个子节点(扇出数 = 1163)。
一个 16KB 的页,扣除页头 / 页尾的固定开销(约 100 字节),可用空间约 16284 字节。
即:每个非叶子节点最多指向 1163 个子节点(扇出数 = 1163)。
2. 3 层 B + 树的层级结构拆解
3 层 B + 树的结构是:「根节点(第 1 层)」→「中间节点(第 2 层)」→「叶子节点(第 3 层)」。
- 第 1 层(根节点):1 个节点,最多指向 1163 个第 2 层节点;
- 第 2 层(中间节点):最多 1163 个节点,每个节点又指向 1163 个第 3 层叶子节点;
- 第 3 层(叶子节点):最多 1163×1163 = 1352569 个节点。
3. 计算叶子节点的单页数据行数
叶子节点存储整行数据(主键索引),假设每行数据大小为 1KB(实际可根据业务调整,比如 512 字节、2KB):
单叶子页可存行数 = 16KB ÷ 1KB = 16 行(若行更小,比如 512 字节,则单页存 32 行)。
单叶子页可存行数 = 16KB ÷ 1KB = 16 行(若行更小,比如 512 字节,则单页存 32 行)。
4. 最终总数据量计算
总数据行数 = 叶子节点数 × 单叶子页行数
= 1163×1163 × 16
≈ 1163² ×16 = 1352569 ×16 ≈ 21641104 行(约 2164 万行)。
= 1163×1163 × 16
≈ 1163² ×16 = 1352569 ×16 ≈ 21641104 行(约 2164 万行)。
三、不同条件下的调整
如果调整参数,结果会变化,举 2 个常见例子:
| 调整条件 | 扇出数 | 叶子单页行数 | 3 层 B + 树总行数 |
|---|---|---|---|
| 主键为 INT(4 字节) | ~1809 | 16 | ≈1809²×16≈5227 万 |
| 行大小为 512 字节 | 1163 | 32 | ≈4328 万 |
| 页大小改为 32KB | ~2327 | 32 | ≈2327²×32≈17298 万 |
总结
- 3 层 B + 树的存储量核心取决于非叶子节点的扇出数(由索引键大小、页大小决定)和叶子节点单页行数(由行大小决定);
- 以默认 16KB 页、BIGINT 主键、1KB 行大小为例,3 层 B + 树约能存2164 万行;
- 实际场景中,因页内开销、行大小差异,实际存储量会略低于理论值,但量级基本一致(千万级)。
-------------------------
这个 100 字节的固定开销,是InnoDB 数据页结构中 “页头 + 页尾” 的通用估算值,并非精确值,本质是对页元数据占用空间的简化描述。
一、先看 InnoDB 数据页的完整结构
InnoDB 的 16KB 数据页,整体分为 7 个部分,其中页头(File Header)、页尾(File Trailer) 是所有类型数据页(不管是叶子节点还是非叶子节点)都必须有的固定结构,再加上少量其他固定元数据,共同构成了这约 100 字节的开销。
| 数据页结构 | 大小(字节) | 作用 |
|---|---|---|
| File Header(页头) | 38 | 存储页的通用信息:页类型(叶子 / 非叶子)、上一页 / 下一页指针、页编号、创建时间等 |
| Page Header | 56(非叶子节点)/ 12(叶子节点) | 存储当前页的特有信息:如非叶子节点的指针数量、叶子节点的记录数等 |
| Infimum/Supermum | 26 | 页内的最小 / 最大虚拟记录,用于界定记录范围,是固定存在的 |
| User Records | 可变 | 实际存储的用户数据(叶子节点)或索引 + 指针(非叶子节点) |
| Free Space | 可变 | 页内的空闲空间 |
| Page Directory | 可变 | 页内记录的目录项,加速记录查找 |
| File Trailer(页尾) | 8 | 存储页的校验和、事务 ID 等,用于校验页的完整性(防止数据损坏) |
二、“约 100 字节” 的估算逻辑
我们以非叶子节点(计算扇出数时用的就是非叶子节点)为例,把固定必有的元数据加起来:
- File Header(38 字节) + File Trailer(8 字节) = 46 字节;
- 加上 Page Header(非叶子节点 56 字节)的核心固定部分;
- 再加上 Infimum/Supermum 的基础开销(26 字节)中的一小部分;
把这些核心固定元数据相加,大概在 90-120 字节 之间,为了计算方便,就统一估算为 100 字节。
三、为什么不用精确值?
- 不同页类型的开销有差异:叶子节点的 Page Header 只有 12 字节,比非叶子节点的 56 字节小很多,精确计算会增加复杂度;
- 相对 16KB 的页大小,100 字节的影响极小:16KB=16384 字节,100 字节仅占 0.6% 左右,对扇出数的计算结果(比如 1163)影响微乎其微,简化估算完全不影响量级结论;
- InnoDB 版本会有微调:不同 MySQL 版本的页结构可能有小改动,元数据大小会有 ±10 字节的波动,用 “约 100 字节” 是行业内的通用简化表述。
总结
- 100 字节是页头、页尾及核心元数据的估算总和,不是精确值;
- 这个估算的目的是扣除固定开销,计算页的可用空间,方便推导扇出数;
- 因为占比极低,无论精确值是 90 还是 120,都不会改变 “3 层 B + 树支撑千万级数据” 的核心结论。