说实话,最近折腾一个日志系统,我在数据库索引上踩了个结结实实的坑。本来想用 MySQL 的 B+Tree 硬扛高并发写入,结果磁盘 IO 直接飙红,最后灰溜溜切到 RocksDB 的 LSM Tree 才把系统救回来。这件事让我意识到,很多人对索引的理解还停留在“只要用 B+Tree 就对了”的阶段——以前的我也是。
今天就来掰扯一下,不同索引在技术实现上到底差在哪,以及为什么选错索引会让人抓狂。
为什么关系型数据库死磕 B+Tree?
机械硬盘时代,一次数据访问里 95% 以上的时间都浪费在寻道上——随机寻道 5 到 10 毫秒,旋转延迟再耗掉 2 到 5 毫秒,真正读取 4KB 数据只要 0.01 毫秒。所以那个年代数据库设计只有一个核心目标:拼命减少磁盘访问次数。
B+Tree 的巧妙之处就在这。所有真实数据放在叶子节点,内部节点只存 key 和指针。同样一个 4KB 的页,B-Tree 只能存约 100 个 key——因为内部节点也存数据,空间被挤占;B+Tree 能存约 300 个,内部节点只放索引,更紧凑。结果是树高直接降低。1000 万行数据,B+Tree 只要 4 次 IO 就能定位目标。对 HDD 来说,从几十次 IO 砍到 4 次,这是质变。
我当时第一次看到这个数字,真拍大腿。一个 4KB 页塞进去几百个 key,一次随机 IO 就能拿到上千条索引信息,寻道开销被均摊到极致。这也是为什么 MySQL InnoDB、PostgreSQL、SQLite 清一色选 B+Tree——不是它完美,而是在那个硬件条件下,它是最优解。
SSD 时代,LSM Tree 凭什么逆袭?
但 SSD 来了之后,游戏规则彻底变了。随机读从 10 毫秒骤降到 0.05 毫秒,IOPS 从 100 飙到百万级,顺序读写带宽更是翻了几十倍。这时候 B+Tree 写入慢的短板被放大了——它每次更新都要随机写磁盘:先读出页、改数据、再写回去。在 SSD 上虽然比 HDD 时代快,但完全没有发挥 SSD 顺序写的碾压优势。
LSM Tree 的思路很骚:别急着更新磁盘,先全缓存在内存里。写入全部顺序追加,然后后台异步合并(Compaction)。数据流是 MemTable(内存)→ SSTable(Level 0)→ Level 1 → Level 2……每一步都是顺序写。这直接让 LSM Tree 的写入性能甩开 B+Tree 一大截。
但代价是什么?读。因为数据分散在不同层的 SSTable 里,一次查询可能要翻好几层再归并结果。这也是为什么 B+Tree 随机读是五星,LSM Tree 只能拿三星。当然 LSM 引擎也不是裸奔——RocksDB 这类实现加了布隆过滤器和多层缓存来兜底点查询,但范围查询依然吃力。
所以选型就是个平衡:日志系统、时序数据库这种写多读少、而且大部分是顺序读旧数据的场景,LSM Tree 是正解;OLTP 业务库那种点查询频繁、对读取延迟敏感的场景,还是 B+Tree 稳。
那些我踩过坑的专用索引
除了 B+Tree 和 LSM,Hash、Bitmap、全文索引和向量索引也各有各的适用边界。这几样我都在实际项目里碰过壁,教训比经验多。
Hash 索引:等值查询无敌,O(1) 直接定位。但我有个同事做过一个用户 ID 缓存系统,用 Redis 的 Hash 上得好好的,某天产品突然提需求要查 “ID 大于 1000 的用户列表”,瞬间傻眼——Hash 完全没法处理范围查询,数据散落在桶里,根本没有任何顺序。最后只能全部推倒,换成 B+Tree 结构的存储。这个教训很贵:Hash 只能干等值查询一件事,用之前先确认未来绝对不会出现范围查询需求。
Bitmap 索引:适合低基数字段的多条件组合查询。性别、地区、状态这种取值有限的字段,位图拉出来直接做位运算,速度快得离谱。我在 ClickHouse 里对一组营销数据做过实验,几千万行的表里按“性别+城市+会员等级”做多维交叉筛选,用 Bitmap比全表扫描查询耗时减少了不止一个数量级。但千万别膨胀——如果拿用户 ID 这种高基数字段建位图,位图长度直接爆炸,存储开销会反噬性能。
全文索引(倒排索引):搜索引擎的核武器。原理是建立 Term 到文档 ID 的映射,配合 posting list 做交集运算。Elasticsearch、MySQL 的 FullText 都靠这个吃饭。但有一个坑:如果你要做的是 LIKE '%abc%' 这种前后缀都模糊的匹配,纯倒排索引帮不了你,得配 Trie 或者 n-gram。选型之前先把自己的查询模式想清楚,别看到“全文索引”四个字就往上套。
向量索引(HNSW / IVF-PQ):这两年 RAG 火起来之后,向量索引成必修课了。HNSW 图索引查询快,复杂度接近 O(log N),但内存占用量惊人——经验上 1TB 向量数据需要 1.3 到 2TB RAM。我跑过一个 500GB 论文的 RAG 项目,HNSW 图索引直接把服务器内存干爆,最后换成 IVF-PQ 才勉强撑住。IVF-PQ 通过量化压缩省内存,但召回率低几个点。选型就是“精度 vs 资源”的博弈,预算有限就得接受取舍。
我的选型心法
回头看,索引选型的核心不是“哪个最好”,而是“你的瓶颈到底在哪”。我自己总结了三步走,后来几次新项目照着推演,踩坑概率确实降了不少:
1. 看读写比例:写多读少 → LSM Tree;读多写少 → B+Tree。
2. 看查询模式:等值查询为主 → Hash;范围查询多 → B+Tree;模糊搜索 → 倒排索引;向量相似 → HNSW。
3. 看硬件:HDD 无脑 B+Tree;SSD 可以考虑 LSM;内存够大才去碰 HNSW。
最后补一句:现实世界都是组合拳。我那个 RAG 项目最后是文档用 PostgreSQL 存,元数据走 B+Tree 索引,向量走 HNSW,冷数据扔 SSD 缓存层——四套索引各司其职,才把系统跑稳。单一索引从来解决不了所有问题,但把每个索引用对地方,就是高手和菜鸟的分水岭。
有道是“知其然还要知其所以然”,索引的“所以然”就是存储介质的物理特性,加上数据结构在时间和空间之间的权衡。下次再选索引,别只看网上的推荐,拿这套框架自己推演一遍,比背答案管用得多。