首页 / MongoDB 入门教程 / 索引基础与 B-Tree

MongoDB 入门教程

索引基础与 B-Tree

本教程共 50 篇 · 第 40 篇 · 更新于 2026-07-30 · 约 4 分钟阅读

MongoDBMongoDB 入门教程索引B-Treeexplain回表

40. 索引基础与 B-Tree

本节目标:理解索引为什么快、MongoDB 索引的结构,并会用 explain() 判断查询是否命中索引。

没有索引时,MongoDB 要逐条扫描集合里每个文档才能找到匹配项。集合一大,查询就慢得没法用。索引(Index)就是为解决这个问题而生的。

40.1 为什么索引能加速查询

索引是一份额外维护的小数据集,只存「字段值 + 文档位置」。它把数据按顺序排好,查的时候不必全表扫,直接「翻目录」定位。

打个比方:一本书没目录,找某个词得逐页翻。有目录后,先查目录再翻到对应页,快得多。索引就是数据库的目录。

Note

索引提升读性能,但拖慢写性能。每次插入、更新、删除,相关索引也要同步更新。写多的集合别建太多索引。

40.2 MongoDB 用 B-Tree 组织索引

MongoDB 的索引底层是 B-Tree(平衡树)数据结构。B-Tree 把索引键按顺序排布,查找、范围查询、排序都能高效完成。

因为有序,B-Tree 能很好支持:

  • 等值匹配:{ age: 28 }
  • 范围查询:{ age: { $gt: 18 } }
  • 排序:sort({ age: 1 })
// 在 age 字段建一个升序索引
test> db.users.createIndex({ age: 1 })

1 表示升序,-1 表示降序。对单字段索引,升序降序差别不大,主要影响排序方向。

40.3 默认就有的 _id 索引

每个集合创建时,MongoDB 都会自动在 _id 上建一个唯一索引(Unique Index)。它保证不会有两条文档 _id 相同。

// 查看集合上的所有索引,含默认的 _id 索引
test> db.users.getIndexes()
Warning

_id 索引不能删除,也不能改成非唯一。它是 MongoDB 保证文档唯一性的最后一道防线。

40.4 用 explain() 看查询是否走索引

explain() 能告诉我们查询的执行计划:扫了多少文档、用没用索引。先插入些示例数据:

// 灌入示例用户
test> db.users.insertMany([
  { _id: 1, name: "小明", age: 28, city: "杭州" },
  { _id: 2, name: "小红", age: 33, city: "上海" },
  { _id: 3, name: "小刚", age: 22, city: "杭州" }
])
// 没建 city 索引时,会全集合扫描(COLLSCAN)
test> db.users.find({ city: "杭州" }).explain("queryPlanner")

queryPlanner.winningPlan 里,stageCOLLSCAN 表示全表扫;建索引后变成 IXSCAN,说明走了索引。

test> db.users.createIndex({ city: 1 })
test> db.users.find({ city: "杭州" }).explain("queryPlanner")
Tip

调慢查询时,第一步就是 explain()stage。看到 COLLSCAN 基本就要加索引了。

40.5 回表:索引查到后还要取文档

索引通常只存部分字段。当查询需要的字段不在索引里,MongoDB 得拿着索引找到的文档位置,回原集合把完整文档取出来,这叫回表(Fetch)。

索引命中 -> 得到文档位置 -> 回集合取完整文档 -> 返回

如果索引本身就包含了查询要的所有字段,就不需要回表。这种「不用回表」的查询,我们叫覆盖查询(Covered Query),第 46 章细讲。

Note

回表有额外开销。高频查询若只需少数几个字段,可考虑让索引顺带覆盖它们,省掉回表。

40.6 小结

索引靠 B-Tree 的有序结构加速查询,代价是拖慢写入。_id 索引默认存在且不能删。用 explain()stage 判断走没走索引,理解回表有助于后续优化覆盖查询。