Mongo 未通过组合两个 IXSCAN 来优化 $or 查询

Mongo doesn't optimize $or query by combining two IXSCANs

我有一个 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]"
            ]
        }
    }
},

三个问题是显而易见的:

你的指数

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

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

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

您的 $or 查询

转述页面 $or Clauses and Indexes:

... for MongoDB to use indexes to evaluate an $or expression, all the clauses in the $or expression must be supported by indexes. Otherwise, MongoDB will perform a collection scan.

简而言之,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. The document {} generates the index key {"": null} for the index with key pattern {"a.b": 1}.
  2. The document {a: []} also generates the index key {"": null} for the index with key pattern {"a.b": 1}.
  3. The document {} matches the query {"a.b": null}.
  4. The document {a: []} does not match the query {"a.b": null}.

Therefore, a query {"a.b": null} that is answered by an index with key pattern {"a.b": 1} must fetch the document and re-check the predicate, in order to ensure that the document {} is included in the result set and that the document {a: []} is not included in the result set.

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