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:
- The document {} generates the index key {"": null} for the index with key pattern {"a.b": 1}.
- The document {a: []} also generates the index key {"": null} for the index with key pattern {"a.b": 1}.
- The document {} matches the query {"a.b": null}.
- 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
。
我有一个 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:
- The document {} generates the index key {"": null} for the index with key pattern {"a.b": 1}.
- The document {a: []} also generates the index key {"": null} for the index with key pattern {"a.b": 1}.
- The document {} matches the query {"a.b": null}.
- 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
。