真实面经题目 · 原创解析
B 树和 B+ 树有什么区别?
B 树和 B+ 树都是面向磁盘页设计的多路平衡搜索树,核心差异不只是数据放在哪里,而是由此带来的扇出、树高、查询路径稳定性、范围扫描能力和索引工程实现差异。数据库索引更偏好 B+ 树,因为它能用更高扇出降低磁盘访问次数,并用叶子节点顺序结构支撑范围查询、排序、覆盖索引和顺序预读。
真实面经题目 · 原创解析
B 树和 B+ 树都是面向磁盘页设计的多路平衡搜索树,核心差异不只是数据放在哪里,而是由此带来的扇出、树高、查询路径稳定性、范围扫描能力和索引工程实现差异。数据库索引更偏好 B+ 树,因为它能用更高扇出降低磁盘访问次数,并用叶子节点顺序结构支撑范围查询、排序、覆盖索引和顺序预读。
可以从三个层次回答。第一,结构上,B 树的关键字和数据记录可以分布在内部节点和叶子节点,搜索可能在内部节点结束;B+ 树的非叶子节点只保存用于路由的键和子指针,所有完整索引项都在叶子节点,叶子节点之间按顺序相连。第二,性能上,B+ 树非叶子节点更小,同样大小的磁盘页能容纳更多分支,树高更低,访问磁盘页次数更少;同时所有查询都走到叶子,路径长度稳定,更适合数据库的页缓存和并发控制。第三,业务查询上,B+ 树做范围查询只要定位到起点叶子,再沿叶子链表顺序扫描即可,而 B 树需要中序遍历多个层级,局部性和预读效果较差。所以数据库索引通常使用 B+ 树,尤其是 MySQL InnoDB 这类存储引擎,主键索引叶子存整行,二级索引叶子存二级键和主键,再通过主键回表。
B 树和 B+ 树都不是二叉树,而是多路平衡搜索树,设计目标是减少磁盘页访问次数。数据库索引面对的瓶颈通常不是 CPU 比较几个键,而是一次次把磁盘页或缓存页读进来,所以树的分叉数量、节点能否塞进一个页、树高是否足够低,比单纯的时间复杂度表达更重要。面试里如果只说一个数据在叶子、一个数据在所有节点,深度是不够的。
B 树的内部节点既可以保存关键字,也可以保存与关键字对应的记录地址甚至记录本身,因此一次查询如果命中内部节点就可以结束。B+ 树把职责拆得更清楚,非叶子节点只作为目录层,用关键字划分子树范围,真实的数据项或记录指针统一放在叶子节点。这个差异会直接影响节点大小、扇出、查询路径和范围扫描方式。
在 B 树中,一个关键字通常只出现在树中的某个节点,内部节点的关键字本身就是可命中的索引项。B+ 树中,非叶子节点里的关键字更多是分隔符或导航键,叶子节点才保存完整索引项,所以同一个边界键可能在内部层和叶子层重复出现。重复并不是浪费的主要矛盾,它换来的是更大的目录扇出和更规整的叶子扫描。
数据库页大小通常固定,节点里放的内容越大,一个页能容纳的键和子指针越少,树的扇出就越低。B 树内部节点如果携带记录信息,会挤占目录空间;B+ 树非叶子节点只放键和指针,同样一页能放更多分支,因此在海量数据下树高更容易保持在很小的范围。树高每少一层,最坏情况下就可能少一次页访问。
B 树做等值查询时,如果目标键刚好在内部节点,确实可能比 B+ 树少走一层;但这种优势不稳定,而且内部节点放数据会降低整体扇出。B+ 树所有查询最终都到叶子节点,看似多走一步,但路径长度一致,行为更可预测,也更符合数据库缓冲池、页锁或闩锁、执行计划成本估算的工程需求。数据库更看重整体稳定收益,而不是偶然命中内部节点。
范围查询是 B+ 树在数据库索引中非常关键的优势。B+ 树先通过目录层定位到范围起点所在叶子页,然后沿着有序叶子节点继续扫描,直到超过范围终点即可。这种访问方式接近顺序读,容易利用预读和缓存。B 树虽然也能按序遍历,但需要在内部节点和子树之间反复切换,访问路径更复杂,磁盘局部性通常更差。
B+ 树叶子节点通常按键顺序维护相邻指针,形成从小到大的有序链式结构,很多存储引擎还会把叶子页作为双向链表维护。这样不仅支持 between、大于小于、order by、group by 的顺序访问,也让覆盖索引扫描更自然。叶子链表不是为了替代树查找,而是在树定位到起点之后,把后续连续读取变成高效的顺序推进。
以常见关系型数据库为例,B+ 树更适合作为磁盘索引的底层结构。聚簇索引中,叶子节点可以直接保存完整行记录;二级索引中,叶子节点通常保存二级索引键和主键值,查到后再用主键去聚簇索引中查整行,这就是回表。若查询字段都在二级索引叶子中,就可以形成覆盖索引,避免回表,这些工程行为都建立在叶子层统一承载索引项的结构上。
二叉树和红黑树的分叉太少,数据量很大时树高会明显增加。数据库索引节点通常对应磁盘页或缓存页,一次访问页的成本远高于页内比较成本。B+ 树通过多路分叉把树高压得很低,几层目录就能管理大量数据,因此更适合磁盘和页缓存模型。
不能绝对这么说。B 树的等值查询如果命中内部节点,理论上可以提前结束;B+ 树一般要走到叶子节点。但数据库场景更看重整体 IO 次数、范围扫描、缓存局部性和稳定性。B+ 树通过更高扇出降低树高,实际综合表现通常更适合索引。
树结构负责快速定位某个键所在的叶子页,链表负责定位之后的连续扫描。范围查询、按索引顺序排序、分页扫描等场景都需要访问一段连续键值。如果没有叶子链表,就要反复从根节点或依赖复杂遍历状态找下一个键,效率和实现都更差。
它确实会让一些分隔键在非叶子节点和叶子节点重复出现,但换来的是目录层更轻、更高扇出和更稳定的扫描方式。数据库索引的主要成本常常是页访问而不是键重复占用的少量空间,因此这种空间换 IO 的设计通常是划算的。
聚簇索引的叶子节点通常保存完整行数据,数据行本身按主键顺序组织;二级索引的叶子节点保存二级索引键以及对应主键值。通过二级索引查到主键后,如果还需要索引中没有的列,就要再到聚簇索引查整行,这个过程通常称为回表。
因为 B+ 树的叶子节点保存完整索引项,并按键有序连接。执行范围查询时,先从根到叶定位起点,然后顺序读取后续叶子页即可;如果排序方向和索引顺序匹配,也能减少额外排序成本。这种顺序访问对磁盘预读和缓存命中都更友好。