【发布时间】:2020-11-28 00:23:59
【问题描述】:
我有一个包含 500k 文档的集合,其结构如下:
{
"_id" : ObjectId("5f2d30b0c7cc16c0da84a57d"),
"RecipientId" : "6a28d20f-4741-4c14-a055-2eb2593dcf13",
...
"Actions" : [
{
"CampaignId" : "7fa216da-db22-44a9-9ea3-c987c4152ba1",
"ActionDatetime" : ISODate("1998-01-13T00:00:00.000Z"),
"ActionDescription" : "OPEN"
},
...
]
}
我需要统计“Actions”数组中的子文档满足特定条件的顶级文档,为此我创建了以下多键索引(仅以“ActionDatetime”字段为例):
db.getCollection("recipients").createIndex( { "Actions.ActionDatetime": 1 } )
问题是当我使用 $elemMatch 编写查询时,操作比我根本不使用 Multikey 索引时慢得多:
db.getCollection("recipients").count({
"Actions":
{ $elemMatch:{ ActionDatetime: {$gt: new Date("1950-08-04")} }}}
)
此查询的统计数据:
{
"executionSuccess" : true,
"nReturned" : 0,
"executionTimeMillis" : 13093,
"totalKeysExamined" : 8706602,
"totalDocsExamined" : 500000,
"executionStages" : {
"stage" : "COUNT",
"nReturned" : 0,
"executionTimeMillisEstimate" : 1050,
"works" : 8706603,
"advanced" : 0,
"needTime" : 8706602,
"needYield" : 0,
"saveState" : 68020,
"restoreState" : 68020,
"isEOF" : 1,
"nCounted" : 500000,
"nSkipped" : 0,
"inputStage" : {
"stage" : "FETCH",
"filter" : {
"Actions" : {
"$elemMatch" : {
"ActionDatetime" : {
"$gt" : ISODate("1950-08-04T00:00:00.000Z")
}
}
}
},
"nReturned" : 500000,
"executionTimeMillisEstimate" : 1040,
"works" : 8706603,
"advanced" : 500000,
"needTime" : 8206602,
"needYield" : 0,
"saveState" : 68020,
"restoreState" : 68020,
"isEOF" : 1,
"docsExamined" : 500000,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 500000,
"executionTimeMillisEstimate" : 266,
"works" : 8706603,
"advanced" : 500000,
"needTime" : 8206602,
"needYield" : 0,
"saveState" : 68020,
"restoreState" : 68020,
"isEOF" : 1,
"keyPattern" : {
"Actions.ActionDatetime" : 1.0
},
"indexName" : "Actions.ActionDatetime_1",
"isMultiKey" : true,
"multiKeyPaths" : {
"Actions.ActionDatetime" : [
"Actions"
]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"Actions.ActionDatetime" : [
"(new Date(-612576000000), new Date(9223372036854775807)]"
]
},
"keysExamined" : 8706602,
"seeks" : 1,
"dupsTested" : 8706602,
"dupsDropped" : 8206602
}
}
}
}
执行此查询需要 14 秒,而如果我删除索引,则 COLLSCAN 需要 1 秒。
我知道不使用 $elemMatch 并直接按“Actions.ActionDatetime”过滤会获得更好的性能,但实际上我需要按数组内的多个字段过滤,所以 $ elemMatch 成为强制性的。
我怀疑是 FETCH 阶段正在扼杀性能,但我注意到当我直接使用“Actions.ActionDatetime”时,MongoDB 能够使用 COUNT_SCAN 而不是 fetch,但性能仍然比 COLLSCAN (4s) 差。
我想知道是否有更好的索引策略来索引数组内具有高基数的子文档,或者我目前的方法是否遗漏了一些东西。 随着数量的增长,索引这些信息将是必要的,我不想依赖 COLLSCAN。
【问题讨论】:
-
$elemMatch如果您只测试数组中的一个字段,则它是多余的。没有 elemMatch 的索引性能如何比较? -
@Joe 是的,我在问题中提到了这一点,但实际上我需要查询多个字段,所以无论如何我都需要 $elemMatch,我只是使用一个字段作为例子。没有 $elemMatch 的性能更好(4 秒),因为规划器使用 COUNT_SCAN 并且不需要 FETCH 任何元素。但是,COLLSCAN 性能仍然更好(1s)。我不知道问题出在 $elemMatch 的使用上,还是多键索引的问题,或者两者兼而有之。
标签: arrays mongodb performance indexing mongodb-indexes