如何使用索引优化 MongoDB 查询性能
摘要
本教程介绍如何通过索引优化 MongoDB 查询性能,演示如何识别慢查询、应用复合索引,以及使用 VisuaLeaf 工具进行可视化管理。内容涵盖查询性能分析、索引推荐策略及常见索引误区。
暂无内容
查看缓存全文
缓存时间: 2026/05/09 09:34
# 如何使用索引优化 MongoDB 查询性能
来源:https://visualeaf.com/blog/mongodb-query-optimization-indexes/
并非所有慢速 MongoDB 查询都是"坏查询"。
有时查询本身没有问题,但 MongoDB 没有合适的索引来辅助数据的过滤、排序和检索。
在本教程中,我们以 *payments* 集合为例。该集合最初没有适合我们查询的有效索引。我们将识别慢速操作,了解推荐的索引,解释为什么复合索引有效,并在 [VisuaLeaf](https://visualeaf.com/?ref=visualeaf.com) 中进行可视化管理。
payments 集合在 VisuaLeaf 中展示了 currency、status、amount 和 paidAt 字段。该集合包含 currency、status、amount、paidAt 等字段,我们将在查询示例中使用它们。工作流程很简单:
**慢速查询 \-\> 查询分析器 \-\> 索引推荐 \-\> 复合索引 \-\> 索引管理器**
当你自己的 MongoDB 集合开始变慢时,也可以使用相同的工作流程。
## 本页目录
1. [慢速查询问题](https://visualeaf.com/blog/mongodb-query-optimization-indexes/#the-slow-query-problem)
2. [我们要优化的 Payments 查询](https://visualeaf.com/blog/mongodb-query-optimization-indexes/#the-payments-query-we-want-to-optimize)
3. [在 VisuaLeaf 中找到慢速查询](https://visualeaf.com/blog/mongodb-query-optimization-indexes/#find-the-slow-query-in-visualeaf)
4. [读取索引推荐](https://visualeaf.com/blog/mongodb-query-optimization-indexes/#read-the-index-recommendation)
5. [为什么这个复合索引有效](https://visualeaf.com/blog/mongodb-query-optimization-indexes/#why-this-compound-index-works)
6. [检查和管理索引](https://visualeaf.com/blog/mongodb-query-optimization-indexes/#check-and-manage-indexes)
7. [应避免的索引错误](https://visualeaf.com/blog/mongodb-query-optimization-indexes/#indexing-mistakes-to-avoid)
8. [常见问题](https://visualeaf.com/blog/mongodb-query-optimization-indexes/#faq)
## 慢速查询问题
当数据库规模较小时,针对它执行的查询会返回很快的结果。
然而,随着数据库规模增大,同样的查询会花费更多时间。这可能是由于需要扫描大量文档、对大量返回值进行排序,或对未建立索引的字段进行求值所导致的。
这些额外的处理步骤会造成查询变慢。例如,考虑如下查询:
``
db.payments.find({
status: "paid"
})
``
如果 "**status**" 字段没有可用的索引,MongoDB 可能会对整个集合进行全量扫描。
MongoDB Explain Plan 显示 payments 查询(按 status 为 paid 过滤)的 COLLSCAN。Explain 视图显示了集合扫描,因为 `status` 查询没有可用索引。并非所有集合扫描都一定有害。对于小型集合,这并无大碍。但对于应用程序每天都在使用的大型集合,这就变得非常重要了。
当你分析查询计划时,有几个步骤需要关注:
| 阶段 | 含义 |
|------|------|
| `COLLSCAN` | MongoDB 扫描了整个集合 |
| `IXSCAN` | MongoDB 使用了索引 |
| `FETCH` | MongoDB 在使用索引后获取了文档 |
| `SORT` | MongoDB 执行了排序操作 |
``
totalDocsExamined: 50000
Returned: 25
``
这意味着 MongoDB 检查了 50,000 个文档,却只返回了 25 个。
目标不仅仅是让查询看起来更整洁,而是减少 MongoDB 的工作负载。
这一技术性的索引功能在官方 [MongoDB 索引文档](https://www.mongodb.com/docs/manual/indexes/?ref=visualeaf.com) 中有详细说明。
## 我们要优化的 Payments 查询
让我们来看一个更贴近实际的例子。
假设你的数据库中存储了 *payments* 数据,你经常需要查询金额超过特定值的已支付 USD 付款,并按支付日期降序排列。
查询如下:
``
db.payments.find({
currency: "USD",
status: "paid",
amount: { $gte: 100 }
}).sort({
paidAt: -1
})
``
这个查询做了四件事:
如果你的 `payments` 集合没有适合该模式的有效索引,MongoDB 就不得不做比必要更多的工作。
| 字段 | 查询的作用 |
|------|-----------|
| `currency` | 只保留 USD 付款 |
| `status` | 只保留已支付的付款 |
| `amount` | 只保留金额大于等于 100 的付款 |
| `paidAt` | 按最新付款优先排序 |
单字段索引可以帮助处理简单的过滤,但这个查询使用了多个字段。
这就是为什么复合索引在这里更合适。它可以同时支持过滤、排序和范围条件。
我们将让 VisuaLeaf 在检查慢速查询后推荐具体的索引。
MongoDB 在官方 [复合索引文档](https://www.mongodb.com/docs/manual/core/indexes/index-types/index-compound/?ref=visualeaf.com) 中对复合索引有详细说明。
## 在 VisuaLeaf 中找到慢速查询
不要因为某个字段看起来重要就为其创建索引。
先找到慢速查询,再针对查询模式建立索引。
在可视化查询构建器中使用上一节中相同的 payments 查询。添加 currency、status 和 amount 的过滤条件,然后按 paidAt 降序排序。
在 VisuaLeaf 中,Explain 视图会显示执行计划、扫描文档数、返回文档数、执行时间和索引使用情况。
VisuaLeaf 查询构建器中的 MongoDB Explain Plan,显示集合扫描和已检查文档数。VisuaLeaf 中的 Explain 视图会显示 MongoDB 查询是否使用了索引,或者执行了集合扫描。如果 MongoDB 扫描了大量文档但只返回少数,那么该查询可能需要一个更好的索引。
### 使用 AI Explain 快速获取摘要
Explain 视图提供了技术细节,但有时你可能想要一个用通俗语言表达的快速摘要。
在 VisuaLeaf 中,AI Explain 可以读取查询分析结果,并解释查询慢的原因。在这个 `payments` 示例中,它检测到了集合扫描,并显示 MongoDB 检查了 298 个文档,只返回了 15 个。
它还建议为查询中使用的字段创建复合索引。
VisuaLeaf AI Explain 显示 payments 查询的集合扫描情况。AI Explain 汇总了集合扫描情况并建议创建索引。*这是一个很有帮助的初步建议,但对于我们的完整查询,由于还需要按 `paidAt` 排序,最终的推荐索引也将 `paidAt` 纳入复合索引中。*
你也可以手动使用 `explain()` 运行相同的检查:
``
db.payments.find({
currency: "USD",
status: "paid",
amount: { $gte: 100 }
}).sort({
paidAt: -1
}).explain("executionStats")
``
查看结果时,关注以下值:
| 指标 | 检查内容 |
|------|---------|
| `totalDocsExamined` | MongoDB 扫描了多少文档 |
| `nReturned` | MongoDB 返回了多少文档 |
| `executionTimeMillis` | 查询花费了多长时间 |
| `winningPlan` | MongoDB 选择了哪个执行计划 |
当 MongoDB 使用索引时查找 IXSCAN,当 MongoDB 扫描集合时查找 COLLSCAN。对于单次查询,Explain 视图已经足够。
对于跨集合的重复慢速操作,请使用 [VisuaLeaf 查询分析](https://visualeaf.com/features/query-profiler/?ref=visualeaf.com)。分析器帮助你查看一段时间内的慢速操作,而不仅仅是你手动测试的单个查询。
VisuaLeaf 的 MongoDB 查询分析仪表板,显示慢速操作。VisuaLeaf 中的查询分析可帮助你识别跨集合的重复慢速 MongoDB 操作。MongoDB 也提供了针对慢速操作的数据库分析功能。你可以在官方 [数据库分析器文档](https://www.mongodb.com/docs/manual/tutorial/manage-the-database-profiler/?ref=visualeaf.com) 中了解更多信息。
## 读取索引推荐
找到慢速查询后,检查哪个索引实际上能有所帮助。
在本例中,`payments` 集合没有适合该查询的有效索引。VisuaLeaf 检测到重复的集合扫描,并根据查询使用的字段推荐一个**复合索引**。
VisuaLeaf 中的 MongoDB 索引推荐,显示针对 payments 集合的推荐复合索引。VisuaLeaf 在检测到 payments 集合上的重复集合扫描后,推荐了一个复合索引。**复合索引**的推荐如下:
``
db.payments.createIndex({
currency: 1,
status: 1,
paidAt: -1,
amount: 1
})
``
这个推荐之所以有用,不仅仅是因为它说"添加一个索引"。
它还给出了具体的字段和顺序。
而顺序正是关键所在。
## 为什么这个复合索引有效
推荐的索引与查询过滤和组织数据的方式相对应。创建推荐的索引后,从 Explain 视图重新运行相同的查询。
VisuaLeaf 中的 MongoDB Explain Plan 显示 payments 查询使用了 IXSCAN 和复合索引。创建复合索引后,VisuaLeaf 显示 MongoDB 已为 payments 查询使用了该索引。现在 MongoDB 使用复合索引,而不再扫描整个集合。
该查询首先按 currency 和 status 字段过滤,然后按 paidAt 排序,再对 amount 应用范围过滤。
换句话说:
| 字段 | 为什么包含在索引中 |
|------|-----------------|
| `currency` | 精确过滤条件 |
| `status` | 精确过滤条件 |
| `paidAt` | 按最新付款排序 |
| `amount` | 范围条件 |
## 检查和管理索引
完成索引后,查看集合中已有的索引。建立索引很容易,也很容易被忽视。一个集合可能会积累不必要的索引、冗余索引,甚至是为已不再运行的查询而创建的索引。在[索引管理器](https://visualeaf.com/docs/index-management?ref=visualeaf.com)中查看你的集合。
VisuaLeaf 中的 MongoDB 索引管理器,显示 payments 集合的索引。索引管理器显示现有 MongoDB 索引的名称、类型、使用情况、属性和状态。索引管理器帮助你查看索引名称、字段、类型、大小、使用情况、属性和状态。
这有助于避免重复创建相同的索引。
当应用程序查询发生变化时,它也有助于你审查旧索引。
你可以将此工作流程与[可视化查询构建器](https://visualeaf.com/blog/build-mongodb-queries-visually/)、[MongoDB Shell](https://visualeaf.com/blog/mongodb-shell-visual-output/)、[聚合管道构建器](https://visualeaf.com/blog/mongodb-aggregation-pipeline-explained-with-examples/)和[图表与仪表板](https://visualeaf.com/blog/mongodb-charts-dashboards/)结合使用,以便测试、分析和展示你的 MongoDB 数据。
如果你愿意,也可以从索引管理器中以可视化方式创建索引,而无需手动编写命令。
在 VisuaLeaf 索引管理器中可视化创建 MongoDB 索引,包含字段方向和索引选项。你可以从索引管理器中以可视化方式创建索引,而无需手动编写命令。
## 应避免的索引错误
索引在与工作负载匹配时才能发挥作用。
在没有充分理由的情况下随意添加索引,反而会带来问题。
1. **为每个字段创建索引**
不要为集合中的每个字段都建立索引。
每个索引都需要存储空间。每个索引在 MongoDB 插入、更新或删除文档时也会增加额外的工作量。
只为应用程序实际运行的查询创建索引。
2. **忽略排序**
查询可能过滤很快,但排序仍然很慢。
示例:
``
db.payments.find({
status: "paid"
}).sort({
paidAt: -1
})
``
一个有效的索引是:
``
db.payments.createIndex({
status: 1,
paidAt: -1
})
``
这同时支持过滤和排序。
3. **字段顺序错误**
复合索引不仅仅是选择正确的字段,顺序同样重要。
对于 payments 查询,以 **amount** 开头不如以 **currency** 和 **status** 开头有效,因为 **amount** 是一个范围条件。
相同的字段,不同的顺序,性能也会不同。
4. **保留不再使用的索引**
你的应用程序会变化。
你的查询会变化。
你的索引也应该随之变化。
定期审查未使用的索引,删除那些不再支持实际查询的索引。
## 总结
MongoDB 查询优化从查询模式开始。
不要靠猜测。先找到慢速查询,检查 MongoDB 是否扫描了过多文档,然后创建一个与查询的过滤、排序和范围操作相匹配的索引。
对于简单的过滤,单字段索引可能就足够了。
对于像 `payments` 示例这样的查询,复合索引通常更好。
借助 VisuaLeaf,你可以在一个地方检测慢速查询、查看索引推荐,并以可视化方式管理 MongoDB 索引。
[免费下载](https://visualeaf.com/download?ref=visualeaf.com)
## 常见问题
#### 为什么我的 MongoDB 查询很慢?
查询慢是因为 MongoDB 扫描了太多文档、排序了太多数据,或者使用了错误的索引。从 `explain("executionStats")` 开始,检查 `totalDocsExamined`、`nReturned` 和获胜计划。
#### MongoDB 查询的最佳索引是什么?
最佳索引与查询使用的字段相匹配。如果查询按一个字段过滤,单字段索引可能有效。如果查询涉及过滤、排序和范围条件,复合索引通常更好。
#### MongoDB 中的复合索引是什么?
复合索引是包含多个字段的索引。当查询使用多个字段进行过滤或排序时,使用复合索引。
示例:
db.payments.createIndex\(\{ currency: 1, status: 1, paidAt: \-1, amount: 1 \}\)
#### MongoDB 中的 ESR 规则是什么?
ESR 代表等值(Equality)、排序(Sort)、范围(Range)。在许多复合索引设计中,将等值字段放在最前,排序字段放在中间,范围字段放在最后。
示例:
currency:等值 status:等值 paidAt:排序 amount:范围
#### 如何判断 MongoDB 是否使用了我的索引?
运行 `explain("executionStats")`。
查找 `IXSCAN`,表示 MongoDB 使用了索引。
查找 `COLLSCAN`,表示 MongoDB 扫描了整个集合。
#### 过多的索引会导致 MongoDB 变慢吗?
会的。过多的索引会增加存储成本和写入开销。为实际查询创建索引,而不是为每个字段都创建。
相似文章
Mongo 的向量搜索性能
本文讨论了 MongoDB 向量搜索功能的性能,可能与其他解决方案进行了比较,或强调了针对 AI 工作负载的改进。
Elixir 应用优化之旅
一位开发者分享了优化 Elixir 应用的经验与教训,重点介绍了针对 Postgres 连接池工具 Ultravisor 的性能改进。文章涵盖了使用火焰图、调用追踪等性能分析技术,以及 eFlambè 和 tprof 等工具。
@0xlelouch_: 2026年PostgreSQL的90%归结于掌握这10个概念:1) MVCC + vacuum。旧行版本保留;a…
本文列举了2026年高效管理PostgreSQL所需的十个核心概念,涵盖MVCC、索引、查询优化和安全。
Opti-Q:一种基于约束的多LLM问题规划优化框架
Opti-Q 是一个受数据库启发的优化器,用于多LLM问答,通过规划执行DAG,在成本、延迟和能量约束下优化答案质量,在基准测试中取得了显著改进。
@barrowjoseph: https://x.com/barrowjoseph/status/2065423284343050314
一篇博客文章重新审视了在智能检索(agentic retrieval)背景下的“慢搜索”概念,认为可以牺牲每次查询的延迟来换取更好的检索质量,从而减少AI代理的整体任务时间和成本。