如何使用索引优化 MongoDB 查询性能

Hacker News Top 工具

摘要

本教程介绍如何通过索引优化 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 的向量搜索性能

Reddit r/LocalLLaMA

本文讨论了 MongoDB 向量搜索功能的性能,可能与其他解决方案进行了比较,或强调了针对 AI 工作负载的改进。

Elixir 应用优化之旅

Lobsters Hottest

一位开发者分享了优化 Elixir 应用的经验与教训,重点介绍了针对 Postgres 连接池工具 Ultravisor 的性能改进。文章涵盖了使用火焰图、调用追踪等性能分析技术,以及 eFlambè 和 tprof 等工具。