【问题标题】:Mongo doesn't optimize $or query by combining two IXSCANsMongo 不会通过组合两个 IXSCAN 来优化 $or 查询
【发布时间】:2017-08-18 18:52:41
【问题描述】:

我有一个 orders 集合,其中包含以下索引:

{location: 1, completedDate: 1, estimatedProductionDate: 1, estimatedCompletionDate: 1}

我正在执行以下查询:

db.orders.find({
  status: {$in: [1, 2, 3]},
  location: "PA",
  $or: [
    {completedDate: {$lt: ISODate("2017-08-22T04:59:59.999Z")}},
    {
      completedDate: null,
      estimatedProductionDate: {$lt: ISODate("2017-08-22T04:59:59.999Z")}
    }
  ]
}).explain()

我希望这会为$or 的每个分支执行有效的IXSCAN,然后合并结果:

        {completedDate: {$lt: ISODate("2017-08-22T04:59:59.999Z")}}

        "indexBounds" : {
            "location" : [
                "[\"TX\", \"TX\"]"
            ],
            "completedDate" : [
                "[MinKey, ISODate("2017-08-22T04:59:59.999Z")]"
            ],
            "estimatedProductionDate" : [
                "[MinKey, MaxKey]"
            ],
            "estimatedCompletionDate" : [
                "[MinKey, MaxKey]"
            ]
        }

        {
            completedDate: null,
            estimatedProductionDate: {$lt: ISODate("2017-08-22T04:59:59.999Z")}
        }

        "indexBounds" : {
            "location" : [
                "[\"TX\", \"TX\"]"
            ],
            "completedDate" : [
                "[null, null]"
            ],
            "estimatedProductionDate" : [
                "[MinKey, ISODate("2017-08-22T04:59:59.999Z")]"
            ],
            "estimatedCompletionDate" : [
                "[MinKey, MaxKey]"
            ]
        }

相反,它只限制IXSCAN 中的location,并在FETCH 期间执行其余的过滤。 有什么方法可以优化此查询而不将其拆分为两个单独的查询?

"winningPlan" : {
    "stage" : "FETCH",
    "filter" : {
        "$and" : [
            {
                "$or" : [
                    {
                        "$and" : [
                            {
                                "completedDate" : {
                                    "$eq" : null
                                }
                            },
                            {
                                "estimatedProductionDate" : {
                                    "$lt" : "2017-08-22T04:59:59.999Z"
                                }
                            }
                        ]
                    },
                    {
                        "completedDate" : {
                            "$lt" : "2017-08-22T04:59:59.999Z"
                        }
                    }
                ]
            },
            {
                "status" : {
                    "$in" : [
                        1,
                        2,
                        3
                    ]
                }
            }
        ]
    },
    "inputStage" : {
        "stage" : "IXSCAN",
        "keyPattern" : {
            "location" : 1,
            "completedDate" : 1,
            "estimatedProductionDate" : 1,
            "estimatedCompletionDate" : 1
        },
        "indexName" : "location_1_completedDate_1_estimatedProductionDate_1_estimatedCompletionDate_1",
        "isMultiKey" : false,
        "isUnique" : false,
        "isSparse" : false,
        "isPartial" : false,
        "indexVersion" : 1,
        "direction" : "forward",
        "indexBounds" : {
            "location" : [
                "[\"TX\", \"TX\"]"
            ],
            "completedDate" : [
                "[MinKey, MaxKey]"
            ],
            "estimatedProductionDate" : [
                "[MinKey, MaxKey]"
            ],
            "estimatedCompletionDate" : [
                "[MinKey, MaxKey]"
            ]
        }
    }
},

【问题讨论】:

    标签: mongodb mongodb-query mongodb-indexes query-planner


    【解决方案1】:

    三个问题立即显而易见:

    你的索引

    我不确定您拥有的其他索引,但您的查询是这样的:

    {
      status:1,
      location:1,
      $or: [
        {completedDate:1},
        {completedDate:1, estimatedProductionDate:1}
      ]
    }
    

    但是您的索引不包含术语 status。您需要索引中的 status 字段来最大限度地利用索引。

    您的 $or 查询

    解释页面$or Clauses and Indexes

    ...为了让 MongoDB 使用索引来评估 $or 表达式,$or 表达式中的所有子句都必须由索引支持。否则,MongoDB 将执行集合扫描。

    简单地说,MongoDB 中高效的$or 查询需要$or 术语作为顶级术语,术语的每个部分都由索引支持。

    例如,您可能会发现以下索引和查询的性能要好一些:

    db.orders.createIndex({
      status:1,
      location:1,
      completedDate:1,
      estimatedProductionDate:1
    })
    
    db.orders.explain().find({
      $or: [
        {
          status: {$in: [1, 2, 3]},
          location: "PA",
          completedDate: {$lt: ISODate("2017-08-22T04:59:59.999Z")}},
        {
          status: {$in: [1, 2, 3]},
          location: "PA",
          completedDate: null,
          estimatedProductionDate: {$lt: ISODate("2017-08-22T04:59:59.999Z")}
        }
      ]
    })
    

    原因是因为 MongoDB 将 $or 查询中的每个术语视为单独的查询。因此,每个术语都可以使用自己的索引。

    请注意,我上面提出的索引中的字段顺序遵循查询中字段的顺序。

    然而,这仍然不是最优的,因为 MongoDB 必须在使用completedDate: null 的查询的索引扫描之后使用filter: {completedDate: {$eq: null}} 执行提取。原因很微妙,最好解释here

    1. 文档 {} 为具有键模式 {"a.b": 1} 的索引生成索引键 {"": null}。
    2. 文档 {a: []} 还为具有键模式 {"a.b": 1} 的索引生成索引键 {"": null}。
    3. 文档 {} 匹配查询 {"a.b": null}。
    4. 文档 {a: []} 与查询 {"a.b": null} 不匹配。

    因此,查询 {"a.b": null} 由带有键的索引回答 模式 {"a.b": 1} 必须获取文档并重新检查谓词, 为了确保文档 {} 包含在结果集中 并且文档 {a: []} 不包含在结果集中。

    为了最大限度地利用索引,最好将 something 分配到 completedDate 字段中,而不是将其设置为 null

    【讨论】:

    • 感谢您的详细解答!我将status 保留在我的索引之外,因为它会干扰通过索引按日期排序。它只有 4 个可能的值,所以我不关心 FETCH 阶段过滤掉不需要的 statuses 的开销。
    • 我不确定索引无法存储 null 是否正确,因为我已经读过唯一索引可以存储 null 条目,并且对于不包含有问题的字段:docs.mongodb.com/manual/core/index-unique
    • 我想我可以为这个查询使用一个包含status 的索引,而对于需要排序的查询,我可以使用一个不包含它的索引。
    • 哎呀,你是对的,null 确实被索引了。更新了答案以反映这一事实。
    • 酷。我不需要区分null 值与缺失字段之间的区别,因此索引应该有效地满足我的查询,包括completedDate: null,对吧?
    猜你喜欢
    • 2019-05-06
    • 2011-06-12
    • 2015-11-26
    • 1970-01-01
    • 2016-11-01
    • 2022-01-23
    • 1970-01-01
    • 1970-01-01
    • 2023-03-27
    相关资源
    最近更新 更多