通过优化索引以消除 MongoDB 中的 "查询目标已超过1000个扫描对象/返回的文档数" 警告

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: MongoDB NoSQL数据库在处理复杂查询时可能出现“查询目标已超过1000个扫描对象/返回的文档数”警告。文章分析了该问题,展示了一个示例集合和相关索引,并提供了查询示例。通过`explain`命令发现查询未有效利用索引。解决方案是遵循ESR规则,创建新索引从而优化查询并消除警告。

概述

MongoDB 是一款功能强大的 NoSQL 数据库,可以灵活扩展以处理大量数据。然而,在处理涉及多个字段和复杂条件的查询时,我们可能会遇到一个警告消息,显示 "查询目标已超过1000个扫描对象/返回的文档数"。在本文中,我们将探讨一个特定的情景,了解此警告的产生原因,并讨论通过优化索引来消除该警告的方法。


问题描述

给定一个MongoDB 集合:

{
    "_id" : ObjectId("abc"),
    "key" : "mykey",
    "val" : "myval",
    "created_at" : ISODate("2023-09-25T07:38:04.985Z"),
    "a_has_sent" : false,
    "b_has_sent" : false,
    "updated_at" : ISODate("2023-09-25T07:38:04.985Z")
}

该集合有两个索引定义如下:

  • 索引1:{"key": {"updated_at": 1}} 名称:"updated_at_1"
  • 索引2:{"key": {"updated_at": 1, "a_has_sent": 1, "b_has_sent": 1}} 名称:"updated_at_1_a_has_sent_1_b_has_sent_1"

"查询目标已超过1000个扫描对象/返回的文档数" (Query Targeting: Scanned Objects / Returned has gone above 1000) 警告是由以下查询触发的:

db.collectionname.find({
    "updated_at": {"$gte": ISODate("2023-09-24")},
    "$or": [
        {"a_has_sent": false},
        {"b_has_sent": false}
    ],
    "key": "key_1"
})

问题研究

我先分析下这个错误 Query Targeting: Scanned Objects / Returned has gone above 1000 产生的原因。依据MongoDB文档 这个警告信息表示你的查询在执行时扫描的文档数量远远超过了实际返回的文档数量,意味着查询的执行效率不高。通常,我们可以通过创建适当的索引来解决这个问题。

针对我们的查询,先查看下 explain 的结果

> db.collectionname.find({ "updated_at": { "$gte": ISODate("2023-09-24")}, "$or": [{ "a_has_sent": false }, {"b_has_sent": false}], "key": "key1"}).explain()

...
          winningPlan: {
            stage: 'FETCH',
            filter: {
              '$and': [
                {
                  '$or': [
                    { a_has_sent: { '$eq': false } },
                    { b_has_sent: { '$eq': false } }
                  ]
                },
                { key: { '$eq': 'key1' } }
              ]
            },
            inputStage: {
              stage: 'IXSCAN',
              keyPattern: { updated_at: 1 },
              indexName: 'updated_at_1',
              isMultiKey: false,
              multiKeyPaths: { updated_at: [] }
            }
          },
          rejectedPlans: [
            {
              stage: 'FETCH',
              filter: {
                '$and': [
                  {
                    '$or': [
                      { a_has_sent: { '$eq': false } },
                      { b_has_sent: { '$eq': false } }
                    ]
                  },
                  { key: { '$eq': 'key1' } }
                ]
              },
              inputStage: {
                stage: 'IXSCAN',
                keyPattern: { updated_at: 1, a_has_sent: 1, b_has_sent: 1 },
                indexName: 'updated_at_1_a_has_sent_1_b_has_sent_1',
...

我们从 winningPlan中观察到使用的索引是 "updated_at_1",而索引 "updated_at_1_a_has_sent_1_b_has_sent_1"是在rejectedPlans,并没有被MongoDB采用,所以导致查询在执行时扫描的文档数量远远超过了实际返回的文档数量。


方案1

我们尝试添加了另一个新索引 {“updated_at”: 1, “key”: 1},希望此查询可以使用新索引来减少扫描的文档数量。不幸的是,我们失败了,这个查询仍然使用了名为"updated_at_1"的索引。


方案2

我们还尝试将find替换为aggregate

> aggregate([{"$match": { "updated_at": { "$gte": ISODate("2023-09-24") }, "$or": [{ "a_has_sent": false }, { "b_has_sent": false}], "key": "key_1"}}])

再次失败,这个查询依然使用了名为"updated_at_1"的索引。


方案3

使用MongoDB The ESR (Equality, Sort, Range) Rule 来优化我们的查询语句

什么是ESR(Equality, Sort, Range)规则

MongoDB的ESR(Equality, Sort, Range)规则是一种用于优化复合索引的方法。复合索引是引用多个字段的索引,可以显著提高查询响应时间。索引键对应于文档字段。在大多数情况下,按照ESR规则来排列索引键有助于创建更高效的复合索引1。

让我们详细了解一下ESR规则的三个方面:

  • Equality(等值):这指的是对单个值的精确匹配。在索引中,首先放置需要精确匹配的字段。索引可以具有多个键,用于处理精确匹配的查询。这些索引键的顺序不影响MongoDB的搜索算法,但为了满足索引的等值匹配,所有精确匹配的索引键必须出现在其他索引字段之前。确保等值测试能够消除至少90%的可能文档匹配,以减少扫描的索引键数量。
  • Sort(排序):排序决定了结果的顺序。排序紧随等值匹配,因为等值匹配减少了需要排序的文档数量。在索引中,只有当查询字段是索引键的子集时,索引才能支持排序操作。如果查询包括了所有前缀键的等值条件,那么对索引键的子集进行排序操作是支持的。例如,如果我们查询汽车制造商为“GM”的文档,并按照型号排序,我们可以创建一个索引:db.cars.createIndex({ manufacturer: 1, model: 1 })。制造商是第一个键,因为它是一个等值匹配。型号按照相同的顺序(1)被索引,以满足查询的需求。
  • Range(范围):范围过滤器用于扫描字段,不需要精确匹配。范围过滤器与索引键的绑定较松。为了提高查询效率,尽量使范围边界尽可能紧凑,并使用等值匹配来限制必须扫描的文档数量。例如,我们可以使用以下查询来查找价格大于等于15000的汽车:`db.cars.find({ price: { $gte: 15000 } })``

遵循ESR规则,我们重新创建了索引{"key": 1, "a_has_sent": 1, "b_has_sent": 1, "updated_at": 1},现在可以使用此索引来执行查询。这个警告"查询目标已超过1000个扫描对象/返回的文档数" (Query Targeting: Scanned Objects / Returned has gone above 1000) 也消除了。

          winningPlan: {
            stage: 'FETCH',
            filter: {
              '$or': [
                { a_has_sent: { '$eq': false } },
                { b_has_sent: { '$eq': false } }
              ]
            },
            inputStage: {
              stage: 'IXSCAN',
              keyPattern: {
                key: 1,
                a_has_sent: 1,
                b_has_sent: 1,
                updated_at: 1
              },
              indexName: 'key_1_a_has_sent_1_b_has_sent_1_updated_at_1',
              isMultiKey: false,
              isUnique: false,
              isSparse: false,
              isPartial: false,
              indexVersion: 2,

总结

  • 通过应用ESR规则,提高了查询效率,并消除了"查询目标已超过1000个扫描对象/返回的文档数"警告。
  • 对于查询语句,需要检查explain结果,查看是否使用了最佳的索引,使查询效率最高。
相关实践学习
MongoDB数据库入门
MongoDB数据库入门实验。
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。   相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
目录
相关文章
|
8天前
|
NoSQL 定位技术 MongoDB
解锁MongoDB索引的秘密:优化查询效率与应对限制的策略
解锁MongoDB索引的秘密:优化查询效率与应对限制的策略
|
8天前
|
NoSQL 定位技术 MongoDB
深入探索 MongoDB:高级索引解析与优化策略
深入探索 MongoDB:高级索引解析与优化策略
|
8天前
|
监控 NoSQL MongoDB
深度优化:掌握 MongoDB 查询分析的关键技巧
深度优化:掌握 MongoDB 查询分析的关键技巧
|
8天前
|
NoSQL BI MongoDB
深入理解 MongoDB 条件操作符:优化查询、精准筛选、提升性能
深入理解 MongoDB 条件操作符:优化查询、精准筛选、提升性能
|
8天前
|
NoSQL BI MongoDB
MongoDB 数据探索之道:查询文档操作详解
MongoDB 数据探索之道:查询文档操作详解
|
29天前
|
存储 SQL NoSQL
什么是 MongoDB,为什么它是当今最受欢迎的数据库之一?
什么是 MongoDB,为什么它是当今最受欢迎的数据库之一?
|
1月前
|
JSON NoSQL MongoDB
理解Nosql数据库的mongodb
【5月更文挑战第5天】MongoDB是2009年发布的一款通用型NoSQL数据库,结合了关系模型和NoSQL的优点,适用于各种现代应用。其特点包括图形界面、数据服务、云基础设施集成(AWS, Azure, Google Cloud)。它具备全面的查询能力、ACID事务、可调整的一致性保证,并有多语言驱动及工具,可在任何地方运行。
206 4
|
1月前
|
存储 NoSQL MongoDB
MongoDB数据库转换为表格文件的Python实现
MongoDB数据库转换为表格文件的Python实现
153 0
|
3天前
|
存储 JSON NoSQL
【文档数据库】ES和MongoDB的对比
【文档数据库】ES和MongoDB的对比
6 1
|
8天前
|
NoSQL JavaScript 安全
精心操作MongoDB:删除数据库的关键步骤和重要事项
精心操作MongoDB:删除数据库的关键步骤和重要事项