你有没有想过一个问题:一个电商平台有几亿条订单记录,你搜一下自己的订单,不到一秒就出来了。几亿条数据啊,一条一条看的话不吃不喝要看几个月,计算机凭什么这么快?
答案就在数据库最核心的设计之一——索引。而绝大多数数据库索引的底层,长着一棵叫 B+ 树的数据结构。
在没有索引的世界
想象你要在一本没有目录的电话簿里找一个叫「张三」的人。你只能从第一页翻到最后一页,平均要翻一半的页数才能找到。这就是顺序查找,时间复杂度 O(n)。数据量翻倍,查找时间也翻倍。几亿条数据用 O(n) 搜,几秒变几小时。
更可怕的场景是:你在电商搜完订单之后,还要按价格排序、按日期筛选。每多一个条件,就要重新翻一遍整本书。这时候你需要的不是一个目录,而是一整套高效的索引系统。
二分查找的局限
讲 B 树之前,得先聊二分查找。如果数据是有序的,你先翻到中间,看目标在左边还是右边,每次砍掉一半数据。这就是二分查找,时间复杂度 O(log n)。在十亿条数据里找一条,只需要 30 次比较(因为 2³⁰ ≈ 十亿)。
三十次对比 十亿次,简直是碾压级的差距。但是二分查找有个致命的前提——数据必须是有序的,而且必须能随机访问。在内存里这没问题,数组按下标访问是 O(1)。可是数据库的数据存在磁盘上啊。磁盘随机访问一次要 5-10 毫秒,比内存慢十万倍。如果你在磁盘上做二分查找,每次「翻到中间」都是一次磁盘寻道,30 次就是 30 次磁盘 I/O,每次 5 毫秒,那就是 150 毫秒。听起来不长?但如果你的查询涉及成百上千次索引查找呢?
磁盘的物理限制
磁盘读一个字节和读连续的一千个字节,速度几乎一样。因为磁盘读写的最小开销在于磁头移动(寻道),不在于传输。所以磁盘的最佳使用方式是一次读一大块连续数据,而不是一次读一点点。
树这种数据结构的问题在于:普通的二叉树每个节点存一个数据,有两个子结点指针。如果把这棵树存在磁盘上,每个节点都要一次 I/O。树有多深,就要多少次 I/O。对于十亿条数据,二叉树深度差不多 30 层,30 次 I/O 已经是二分查找的水平。但如果我们让每个节点存很多个键值呢?
B 树的核心理念
B 树的核心思想就是一个节点里存很多个有序的键,同时有很多个子结点指针。一个节点对应磁盘上的一个「块」,你一次 I/O 就把整个块读进内存,然后在内存里对这个节点的几十个键做二分查找,找到下一步去哪个子节点,再读下一个块。
假设一个节点存了 500 个有序键,那十亿条数据的树深度会变成 log₅₀₀(十亿) ≈ 4 层。也就是说,从树的根节点走到叶子节点,只需要 4 次磁盘 I/O。4 次 vs 30 次,这就是 B 树比二叉树快一个数量级的原因。
B+ 树的改进
B+ 树是 B 树的变种,几乎所有现代数据库都在用。它的区别有两个:
第一,非叶子节点只存键值用于导航,不存实际数据。这样同样大小的磁盘块可以塞进更多的键,树更矮,I/O 更少。
第二,叶子节点之间用指针连成了链表。这意味着当你需要「范围查询」(比如查 2026年1月到3月的所有订单),找到第一个匹配的叶子节点之后,顺着链表指针依次往后读就行了,不用再回头往树根爬。
没有 B+ 树的话,范围查询需要反复从根走到叶,每次都重新开始。有了叶子链表,范围查询的效率从 O(log n × m) 降到 O(log n + m),其中 m 是结果条数。这对电商、日志分析、时间序列数据来说,是本质性的提升。
索引不是免费的
B+ 树虽好,但代价是写操作变慢。每插入一条数据,不仅要写数据本身,还要维护索引树的平衡——可能触发节点的分裂、父节点的更新。这就是为什么数据库表不能给每一列都建索引。
一个典型的权衡是:读多写少的表(比如文章内容表),可以多建索引;写多读少的表(比如日志流水表),索引要精简。InnoDB 引擎默认用 B+ 树做聚集索引,数据行本身就挂在叶子节点上,主键查找只需要一次索引遍历就能拿到完整行数据,这是 MySQL 这么快的秘密之一。
结语
B 树是计算机科学中少有的几个在理论上优美、在实践中坚不可摧的设计之一。它不是一个天才半夜灵光一现想出来的,而是工程在物理世界限制下逐渐演化的最优解。它懂磁盘的物理特性,所以让每个节点尽可能塞满信息以减少 I/O;它懂人类的查询模式,所以用叶子链表优化范围扫描。
下次你搜订单秒出结果的时候,可以想象一下:磁盘磁头在几毫米的行程内完成了四次精确定位,B+ 树像一本分了很多层的超厚目录,每层都在说「往左、往右、再往左」,四步之后准确停在你想要的那条数据面前。这大概是工程学最接近魔术的时刻。