MongoDB-count()尽管使用了索引,但花费的时间太长
我收集了62k份文件。同一个集合也有一组索引,其中大多数是简单的单字段索引。我所观察到的是,以下查询需要非常长的时间才能返回:MongoDB-count()尽管使用了索引,但花费的时间太长,mongodb,indexing,query-optimization,Mongodb,Indexing,Query Optimization,我收集了62k份文件。同一个集合也有一组索引,其中大多数是简单的单字段索引。我所观察到的是,以下查询需要非常长的时间才能返回: db.jobs.count({"status":"complete","$or":[{"groups":{"$exists":false}},{"groups":{"$size":0}},{"groups":{"$in":["5e65ffc2a1e6ef0007bc5fa8"]}}]}) 上述查询的executionStats如下所示 { "queryPlanner"
db.jobs.count({"status":"complete","$or":[{"groups":{"$exists":false}},{"groups":{"$size":0}},{"groups":{"$in":["5e65ffc2a1e6ef0007bc5fa8"]}}]})
上述查询的executionStats如下所示
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "xxxxxx.jobs",
"indexFilterSet" : false,
"parsedQuery" : {
"$and" : [
{
"$or" : [
{
"groups" : {
"$size" : 0
}
},
{
"groups" : {
"$eq" : "5e65ffc2a1e6ef0007bc5fa8"
}
},
{
"$nor" : [
{
"groups" : {
"$exists" : true
}
}
]
}
]
},
{
"status" : {
"$eq" : "complete"
}
}
]
},
"winningPlan" : {
"stage" : "FETCH",
"filter" : {
"$or" : [
{
"groups" : {
"$size" : 0
}
},
{
"groups" : {
"$eq" : "5e65ffc2a1e6ef0007bc5fa8"
}
},
{
"$nor" : [
{
"groups" : {
"$exists" : true
}
}
]
}
]
},
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"status" : 1,
"groups" : 1
},
"indexName" : "status_1_groups_1",
"isMultiKey" : true,
"multiKeyPaths" : {
"status" : [ ],
"groups" : [
"groups"
]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"status" : [
"[\"complete\", \"complete\"]"
],
"groups" : [
"[MinKey, MaxKey]"
]
}
}
},
"rejectedPlans" : [
{
"stage" : "FETCH",
"filter" : {
"$or" : [
{
"groups" : {
"$size" : 0
}
},
{
"groups" : {
"$eq" : "5e65ffc2a1e6ef0007bc5fa8"
}
},
{
"$nor" : [
{
"groups" : {
"$exists" : true
}
}
]
}
]
},
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"status" : 1
},
"indexName" : "status_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"status" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"status" : [
"[\"complete\", \"complete\"]"
]
}
}
}
]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 62092,
"executionTimeMillis" : 9992,
"totalKeysExamined" : 62092,
"totalDocsExamined" : 62092,
"executionStages" : {
"stage" : "FETCH",
"filter" : {
"$or" : [
{
"groups" : {
"$size" : 0
}
},
{
"groups" : {
"$eq" : "5e65ffc2a1e6ef0007bc5fa8"
}
},
{
"$nor" : [
{
"groups" : {
"$exists" : true
}
}
]
}
]
},
"nReturned" : 62092,
"executionTimeMillisEstimate" : 9929,
"works" : 62093,
"advanced" : 62092,
"needTime" : 0,
"needYield" : 0,
"saveState" : 682,
"restoreState" : 682,
"isEOF" : 1,
"invalidates" : 0,
"docsExamined" : 62092,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 62092,
"executionTimeMillisEstimate" : 60,
"works" : 62093,
"advanced" : 62092,
"needTime" : 0,
"needYield" : 0,
"saveState" : 682,
"restoreState" : 682,
"isEOF" : 1,
"invalidates" : 0,
"keyPattern" : {
"status" : 1,
"groups" : 1
},
"indexName" : "status_1_groups_1",
"isMultiKey" : true,
"multiKeyPaths" : {
"status" : [ ],
"groups" : [
"groups"
]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : false,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"status" : [
"[\"complete\", \"complete\"]"
],
"groups" : [
"[MinKey, MaxKey]"
]
},
"keysExamined" : 62092,
"seeks" : 1,
"dupsTested" : 62092,
"dupsDropped" : 0,
"seenInvalidated" : 0
}
}
},
"serverInfo" : {
"host" : "xxxxxxx",
"port" : 27017,
"version" : "3.6.15",
"gitVersion" : "xxxxxx"
},
"ok" : 1}
我想理解的是,为什么当输入_阶段的索引扫描需要60毫秒时,FETCH阶段需要10秒钟。因为我最终要做一个count(),我并不需要mongoDB来返回文档,我只需要它对匹配的键的数量进行$求和,并给出总数
知道我做错了什么吗 查询解释说没有计数,它返回了很多文档:
"nReturned" : 62092,
db.jobs.update({"groups":{"$exists":false}},{"$set":{"groups":false}})
db.jobs.count({"status":"complete","groups":{"$size":0}})
db.jobs.count({"status":"complete","groups":{"$in":[false, "5e65ffc2a1e6ef0007bc5fa8"]}})
每个阶段的估计执行时间表明,索引扫描预计需要60毫秒,从磁盘获取文档需要额外的9.8秒
此计数要求获取文档有几个原因:
"nReturned" : 62092,
db.jobs.update({"groups":{"$exists":false}},{"$set":{"groups":false}})
db.jobs.count({"status":"complete","groups":{"$size":0}})
db.jobs.count({"status":"complete","groups":{"$in":[false, "5e65ffc2a1e6ef0007bc5fa8"]}})
- 无法从索引中完全确定是否存在键
谓词也很麻烦。构建索引时,文档的值包含每个索引字段的值。“不存在”没有值,因此它使用{“$exists”:false}
。由于包含值显式设置为null
的字段的文档不应与null
匹配,因此查询执行器必须从磁盘加载每个文档,以确定该字段是{“$exists”:false}
还是不存在。这意味着不能使用COUNTSCAN阶段,这进一步意味着必须从磁盘加载所有要计数的文档null
谓词不能确保排他性$或
查询执行者不能提前知道$或
中的子句是互斥的。它们在查询中,但在一般情况下,单个文档可能与
$或
中的多个子句相匹配,因此查询执行器必须加载文档以确保重复数据消除
$in
子句进行查询,或者只使用$size
子句进行查询,您应该会发现计数是从索引扫描派生的,而无需加载任何文档
也就是说,如果要从客户端单独运行这些查询,并对结果求和,您应该会发现总体执行时间小于需要获取的查询:
db.jobs.count({"status":"complete","groups":{"$size":0}})
db.jobs.count({"status":"complete","groups":{"$in":["5e65ffc2a1e6ef0007bc5fa8"]}})
对于{“groups”:{“$exists”:false}
谓词,您可以稍微修改数据,例如确保字段始终存在,但为其指定一个表示“未定义”的值,该值可以索引和查询
例如,如果要运行以下更新,则“组”字段将存在于所有文档中:
"nReturned" : 62092,
db.jobs.update({"groups":{"$exists":false}},{"$set":{"groups":false}})
db.jobs.count({"status":"complete","groups":{"$size":0}})
db.jobs.count({"status":"complete","groups":{"$in":[false, "5e65ffc2a1e6ef0007bc5fa8"]}})
通过运行这两个查询,您可以获得与上述计数相同的结果,这两个查询都应该包含在索引扫描中,并且应该比需要加载文档的查询一起运行得更快:
"nReturned" : 62092,
db.jobs.update({"groups":{"$exists":false}},{"$set":{"groups":false}})
db.jobs.count({"status":"complete","groups":{"$size":0}})
db.jobs.count({"status":"complete","groups":{"$in":[false, "5e65ffc2a1e6ef0007bc5fa8"]}})
如果您能够以某种方式避免空数组的情况,那么可以使用以下查询:
db.jobs.count({“status”:“complete”,“groups”:{$in:[null,“5e65ffc2a1e6ef0007bc5fa8”]})
null
相当于$exists:false
另外:我建议使用ObjectId而不是string作为组
字段的类型
更新
$size从未命中索引
您可以使用以下查询:
db.jobs.count({"status":"complete","$or":[
{"groups":[],
{"groups": {$in: [ null, "5e65ffc2a1e6ef0007bc5fa8" ]}
]})
`
请对您的文本进行更好的格式化,并解释您与其他解决方案的不同之处。谢谢。除了一些小的语法错误之外,这个管道还有一个问题——你不能以这种方式使用$count。根据mongoDB文档:$count具有以下原型形式:{$count:}是输出字段的名称,该字段的值为count。虽然此代码可能提供了一个问题的解决方案,但最好添加上下文以了解其工作的原因/方式。这可以帮助未来的用户学习,并将这些知识应用到他们自己的代码中。当代码被解释时,用户可能会以投票的形式给予你积极的反馈;显然不需要$in,因为$exists是一个更广泛的标准。。。简体中文:计数状态=包含字段组的所有记录/文档的完成。。。。是吗?不完全是,父条件是$or,因此它要么根本不是组字段,要么是空数组,要么是$INU中的一个。幸运的是,数据库中的大多数文档都有[]作为组值,因此我无法真正避免它。。关于ObjectId建议-这会带来什么影响?使用ObjectId而不是普通字符串更安全还是更快?ObjectId更小,索引性能更好。哦,伙计,我对你的答案寄予厚望!但不幸的是,我试图删除这些麻烦的谓词,但没有效果。运行
db.jobs.find({“status”:“complete”,“groups”:{“$size”:0}})。explain(“executionStats”)
给了我inputStage.executionTimeMillisEstimate:50,但父节点获取阶段仍然花费了>6秒的时间。你知道为什么它没有使用COUNTSCAN阶段吗?仔细看,“groups”:{“$size”:0}
不能由索引正常提供服务,因为MongoDB没有将数组本身存储在索引值中,它为每个数组元素存储单独的索引值。我怀疑实现这一点的方法是在更新中结合[]
和missing
,比如db.jobs.update({$or:[{“groups”:{“$exists”:false},{“groups”:[]},{“$set”:{“groups”:false})
,则第二个查询可以在不加载文档的情况下获得所需的计数。您知道mongoDB将如何处理在“groups”字段中实际包含哈希数组的文档吗?如果文档包含组:[“foo”、“bar”、“abc”]
且查询筛选器为