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

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
简介: 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
目录
相关文章
|
1天前
|
存储 NoSQL MongoDB
微服务——MongoDB常用命令——MongoDB索引的类型
本节介绍了MongoDB中索引的几种类型及其特点。包括单字段索引,支持升序/降序排序,索引顺序对操作无影响;复合索引,字段顺序重要,可实现多级排序;地理空间索引,支持平面与球面几何查询;文本索引,用于字符串搜索并存储词根;哈希索引,基于字段值散列,适合等值匹配但不支持范围查询。
13 1
微服务——MongoDB常用命令——MongoDB索引的类型
|
2天前
|
存储 NoSQL 定位技术
MongoDB索引知识
MongoDB索引是提升查询性能的关键工具,通过构建特殊的数据结构(如B树)优化数据访问路径。无索引时,查询需全集合扫描,时间复杂度为O(n);使用索引后可降至O(log n),实现毫秒级响应。MongoDB支持多种索引类型:单字段索引适用于高频单字段查询;复合索引基于最左前缀原则优化多条件过滤和排序;专业索引包括地理空间索引(支持LBS服务)、文本索引(全文搜索)和哈希索引(分片键优化)。合理选择和优化索引类型,可显著提升数据库性能。建议使用explain()分析查询计划,并定期清理冗余索引。
33 14
|
1天前
|
NoSQL MongoDB 微服务
微服务——MongoDB常用命令——文档的分页查询
本文介绍了文档分页查询的相关内容,包括统计查询、分页列表查询和排序查询。统计查询使用 `count()` 方法获取记录总数或按条件统计;分页查询通过 `limit()` 和 `skip()` 方法实现,控制返回和跳过的数据量;排序查询利用 `sort()` 方法,按指定字段升序(1)或降序(-1)排列。同时提示,`skip()`、`limit()` 和 `sort()` 的执行顺序与编写顺序无关,优先级为 `sort()` > `skip()` > `limit()`。
12 1
|
1天前
|
JSON NoSQL MongoDB
微服务——MongoDB常用命令——文档基本CRUD
本文介绍了MongoDB中文档的基本操作,包括插入、查询、更新和删除。单个文档插入使用`insert()`或`save()`方法,批量插入用`insertMany()`。查询所有文档用`find()`,条件查询可在`find()`中添加参数,投影查询控制返回字段。更新文档通过`update()`实现,支持覆盖修改、局部修改(使用`$set`)和批量修改。列值增长可用`$inc`实现。删除文档用`remove()`,需谨慎操作以免误删数据。此外,文档键值对有序,区分大小写,不能有重复键。
16 1
|
1天前
|
NoSQL 测试技术 MongoDB
微服务——MongoDB实战演练——根据上级ID查询文章评论的分页列表
本节介绍如何根据上级ID查询文章评论的分页列表,主要包括以下内容:(1)在CommentRepository中新增`findByParentid`方法,用于按父ID查询子评论分页列表;(2)在CommentService中新增`findCommentListPageByParentid`方法,封装分页逻辑;(3)提供JUnit测试用例,验证功能正确性;(4)使用Compass插入测试数据并执行测试,展示查询结果。通过这些步骤,实现对评论的高效分页查询。
12 0
|
1天前
|
存储 NoSQL MongoDB
微服务——MongoDB常用命令——MongoDB索引知识概述
本文介绍MongoDB索引相关知识,包括其在查询中的重要作用。索引可避免全集合扫描,显著提升查询效率,尤其在处理海量数据时。通过B树数据结构存储字段值并排序,支持相等匹配、范围查询及排序操作。文中还提供了官方文档链接以供深入学习。
14 0
|
2月前
|
SQL NoSQL Java
Java使用sql查询mongodb
通过MongoDB Atlas Data Lake或Apache Drill,可以在Java中使用SQL语法查询MongoDB数据。这两种方法都需要适当的配置和依赖库的支持。希望本文提供的示例和说明能够帮助开发者实现这一目标。
70 17
|
2月前
|
存储 监控 NoSQL
【赵渝强老师】MongoDB文档级别的并发控制
MongoDB使用WiredTiger存储引擎在文档级别进行并发控制,允许多个写操作同时修改不同文档,但对同一文档的修改需序列化执行。引擎采用乐观锁和意向锁机制处理冲突。通过视频讲解、插入大量文档示例及使用`mongotop`和`db.serverStatus()`命令,演示了如何监控MongoDB的锁信息和读写统计,展示了数据库和集合级别的写锁情况。
107 29
|
3月前
|
SQL NoSQL Java
Java使用sql查询mongodb
通过使用 MongoDB Connector for BI 和 JDBC,开发者可以在 Java 中使用 SQL 语法查询 MongoDB 数据库。这种方法对于熟悉 SQL 的团队非常有帮助,能够快速实现对 MongoDB 数据的操作。同时,也需要注意到这种方法的性能和功能限制,根据具体应用场景进行选择和优化。
160 9
|
4月前
|
存储 NoSQL 关系型数据库
MongoDB索引知识
MongoDB索引知识
46 1
MongoDB索引知识