图 1:本文封面,点明题目与三个关键词。
先说明属性:这是一篇经典题解析,题目出自本地历史面试资料(见文末来源说明),与任何公司的当前招聘无关。这道题几乎是数据库面试的开场题,但多数人停在「B+ 树矮、IO 少」这一句结论上,被追问「矮多少、少几次」就露怯了。
想象面试现场,面试官问:「数据库索引为什么用 B+ 树,用平衡二叉树不行吗?」
第一步,审题。
考察点不是背两种树的定义,而是能不能从存储介质出发做推导:索引存在磁盘上,读数据的单位是页,一次查询要读几页取决于树的高度。能自己推出「树高决定 IO 次数」,再算出两种结构的具体差距,这题才算答到位。
第二步,基础回答:先把磁盘这个前提摆上桌面。
内存里查找,平衡二叉树(如 AVL 树)和 B+ 树的比较次数都是 O(log n),差距不大。但数据库索引太大,主体在磁盘上。磁盘读写的特点是:一次 IO 读一整页,代价远大于内存比较;MySQL 官方文档写明 InnoDB 索引页的默认大小是 16KB。所以评价索引结构的第一指标不是比较次数,而是「从根到叶子要读几个页」——也就是树高。
平衡二叉树每个节点只存一个 key,分叉数是 2。一千万条记录,树高约 log2(10^7) ≈ 24 层,一次点查询最坏要读二十几个页。即使上层页能缓存进内存,靠近叶子的几层大概率要真正读盘。
B+ 树把每个节点做成一整页:内部页只存「key + 子页指针」,不存数据行。一页 16KB,假设一个 key 加指针占几十个字节,单页就能放下几百到上千个分叉。扇出按一千估算,一千万条记录的树高只要 3 层左右:一次点查询从根到叶子只需读 3 个页;其中根页和中间页通常常驻内存,真正落盘的磁盘 IO 往往只有一次。同样的数据量,24 层对 3 层,这就是结论的来源。
图 2:同样的数据量,二叉树瘦高、B+ 树矮胖,层级差距就是 IO 差距。
第三步,B+ 树的另一半优势:叶子页的结构。
MySQL 官方文档说明,InnoDB 的索引记录都存在叶子页上,数据行则挂在聚簇索引(通常就是主键)的叶子页里。B+ 树的叶子页之间还有链表相连,范围查询(比如 BETWEEN、排序分页)找到一个起点后顺着链表顺序读就行;二叉树做范围扫描要在树里反复中序遍历,定位效率完全不在一个档次。文档还确认:二级索引的每条记录都带着主键值,用于回查聚簇索引——这解释了为什么主键要尽量短,也顺带解释了为什么页大小和扇出会反过来约束索引 key 的长度上限(这一点此处只提一句,不展开)。
第四步,可能的追问。
「为什么不用哈希索引?」等值查询哈希确实 O(1),但不支持范围、排序和最左前缀匹配,通用性输得彻底。「B 树和 B+ 树差在哪?」B 树的内部节点也存数据,扇出被压低、树变高,且范围扫描不如叶子链表顺。「树高 3 层是固定的吗?」不是,它由行数、行长和页大小共同决定,数据量再涨两个数量级树也只是从 3 层长到 4 层——扇出大,树高的增长极其缓慢。
工程应用与 AI 关联。
「固定页预算下,扇出决定层数,层数决定 IO」这条推理链是存储系统的通用语言。AI 应用里存 embedding 元数据、样本索引时面对的是同一道题;向量索引 IVF 用倒排列表控制一次查询扫描的分区数,HNSW 用分层图控制跳数,本质都是在「树高—IO 次数」这个坐标系上做权衡。看懂 B+ 树,再看这些结构会觉得似曾相识。
易错点清单。
一,只说「B+ 树更快」而不提磁盘页这个前提——内存场景下结论并不成立。二,把树高说成拍脑袋的数字,讲不出「页大小除以条目大小等于扇出」的推导。三,漏掉叶子链表,答不出范围查询为什么也受益。四,把哈希索引当成更优解,忽略范围与排序需求。
回到面试现场,建议的答题节奏:先立前提(索引在磁盘、读盘按页、页默认 16KB),再算两笔账(二叉树约 24 层、B+ 树约 3 层),然后补叶子链表对范围查询的支持,最后收在「评价指标是树高与 IO 次数」上。从介质特性推出结构选择,才是这题想看到的回答。
如果面试官继续追问「数据涨到百亿行,B+ 树还够用吗,下一步该怎么办」,你会从哪些方向延伸?欢迎写出你的思路。
参考资料
- The Physical Structure of an InnoDB Index(MySQL 8.4 官方文档:InnoDB 索引为 B-tree 结构、索引记录存于叶子页、索引页默认 16KB、页大小由 innodb_page_size 在建实例时确定) — https://dev.mysql.com/doc/refman/8.4/en/innodb-physical-structure.html
- Clustered and Secondary Indexes(MySQL 8.4 官方文档:聚簇索引即主键、二级索引记录包含主键列用于回查、主键越短二级索引越省空间) — https://dev.mysql.com/doc/refman/8.4/en/innodb-index-types.html