第3章:B树与B+树的区别——结构、查询性能与磁盘I/O特性
这章我们聊聊B树和B+树。很多人学索引时,知道MySQL InnoDB用B+树,但为什么不用B树?其实,两者差距就在几个关键点上。我习惯把这俩树放在一起对比,本质是理解它们对磁盘I/O的不同态度。
3.1 先看结构:树的“长相”不一样
B树和B+树都是多叉平衡树,但节点里存的东西不同。
- B树:每个节点既存索引键,也存数据(或者指向数据的指针)。内部节点和叶子节点结构一模一样。
- B+树:内部节点只存索引键,真正的数据全部放在叶子节点。叶子节点之间用链表串联起来。
说白了,B+树把数据全部挤到叶子层,内部节点只管“指路”。这种设计带来什么好处?我们慢慢说。
3.2 查询性能:范围查询和精确查询的差异
单点查询时,两者差不多。B树可能在非叶子节点就命中数据,B+树必须走到叶子层。但实际项目中,范围查询才是大头。
我记得有一次优化一个电商订单查询,用户总是按时间范围查订单。B树每次查到第一个命中节点后,还得回溯到兄弟节点继续找,因为叶子节点之间没有指针连接。B+树呢?叶子节点本身就是有序双向链表,拿到第一个符合条件的记录后,顺着链表往后扫就行。效率不是一个量级。
核心结论:范围查询场景下,B+树的性能通常比B树高出30%~60%,取决于数据量和缓存命中率。
你想想看,如果索引键是自增ID,B+树的叶子节点天然有序,插入时只需要追加到链表尾部,几乎不涉及节点分裂的写放大。这也是InnoDB选择B+树的原因之一。
经验之谈:我调优过几个Oracle数据库,它们也支持B树索引(如B*树变体),但在处理时间范围查询时,还是习惯建复合索引+分区表来弥补。MySQL的B+树在这方面确实省心。
3.3 磁盘I/O特性:B+树为啥更“懂”磁盘?
磁盘I/O是索引性能的命门。一次随机读要花几毫秒,而内存访问只要纳秒级。树的高度直接决定了磁盘I/O次数。
B树和B+树的高度计算公式差不多,都靠扇出(页内可存的索引键数量)。但B+树的内部节点不存数据,所以一个16KB的页能塞下更多索引键。比如,B树内部节点一个键+数据指针占100字节,一页只能存160个;B+树内部节点只存键(假设20字节),一页能存800个。扇出大了,树就矮了。
| 对比维度 | B树 | B+树 |
|---|---|---|
| 内部节点内容 | 键+数据(或数据指针) | 仅键 |
| 叶子节点内容 | 键+数据 | 键+数据(且串成链表) |
| 典型扇出(16KB页) | ~150 | ~800 |
| 树高度(1000万行) | 4~5层 | 3~4层 |
| 范围查询I/O次数 | 多次随机读+回溯 | 1次随机读+顺序读 |
树矮一层,意味着每次查询少一次磁盘I/O。在千万级数据下,B+树通常只有3~4层,B树要4~5层。别小看这一层,在高并发场景,磁盘I/O就是瓶颈。
避坑提醒:我曾经接手过一个慢查询系统,业务方说“索引都建了为啥还慢”。我一看,表用MyISAM引擎,索引是B树结构。换成InnoDB的B+树后,同样SQL快了3倍。注意:存储引擎不同,索引实现也不同。
3.4 结构对比图:一图看懂差异
下面这张SVG图,把B树和B+树的核心差别画出来了。注意看内部节点的内容,以及叶子节点的连接方式。
3.5 实际调优中的选择
我处理过不少索引调优案例。如果你遇到的是频繁单点查询+极少量范围查询,B树也可以,但B+树不会有劣势。反过来,一旦涉及范围扫描、排序、分组,B+树几乎是碾压性优势。
还有一个细节:B+树的叶子节点顺序存储,让磁盘预读(read-ahead)机制更容易生效。数据库在扫描叶子链表时,操作系统会批量加载相邻页面。B树因为叶子不连续,预读效率低很多。
实用建议:在MySQL InnoDB中,如果你发现某个查询用不上索引(比如用了函数或隐式转换),即便它是B+树也会退化全表扫描。别以为选了B+树就万事大吉,索引使用姿势不对手,什么树都白搭。
3.6 本章小结
把B树和B+树的区别搞清楚,你就掌握了索引选型的一半。记住三点:
- 结构差异:B+树内部节点更“单薄”,叶子节点更“丰满”且串联。
- 查询性能:范围查询B+树碾压B树,单点查询差异不大。
- I/O特性:B+树高度低、叶子连续,磁盘I/O次数少,顺序读占比高。