在Amazon EC2上运行的测试mongodb(版本3.0.1)(3.14.33-26.47.amzn1.x86_64,t2.medium:2 vcpus,4G mem)。
收集“access_log”(约40,000,000条记录,每天1,000,000条记录),以及一些索引:
...
db.access_log.ensureIndex({ visit_dt: 1, 'username': 1 })
db.access_log.ensureIndex({ visit_dt: 1, 'file': 1 })
...
当执行以下“聚合”时,它非常慢(需要几个小时):
db.access_log.aggregate([
{ "$match": { "visit_dt": { "$gte": ISODate('2015-03-09'), "$lt": ISODate('2015-03-11') } } },
{ "$project": { "file": 1, "_id": 0 } },
{ "$group": { "_id": "$file", "count": { "$sum": 1 } } },
{ "$sort": { "count": -1 } }
])
此聚合的所有字段 在第二个索引中都是包含({visit_dt:1,'file':1},即“visit_dt_1_file_1”)。< / p>
所以我很困惑,为什么mongodb不使用这个索引,而是另一个。
在解释计划时,总是得到以下信息,我根本不了解。
你可以帮忙吗?非常感谢!> db.access_log.aggregate([
... { "$match": { "visit_dt": { "$gte": ISODate('2015-03-09'), "$lt": ISODate('2015-03-11') } } },
... { "$project": { "file": 1, "_id": 0 } },
... { "$group": { "_id": "$file", "count": { "$sum": 1 } } },
... { "$sort": { "count": -1 } }
... ], { explain: true } );
{
"stages" : [
{
"$cursor" : {
"query" : {
"visit_dt" : {
"$gte" : ISODate("2015-03-09T00:00:00Z"),
"$lt" : ISODate("2015-03-11T00:00:00Z")
}
},
"fields" : {
"file" : 1,
"_id" : 0
},
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "xxxx.access_log",
"indexFilterSet" : false,
"parsedQuery" : {
"$and" : [
{
"visit_dt" : {
"$lt" : ISODate("2015-03-11T00:00:00Z")
}
},
{
"visit_dt" : {
"$gte" : ISODate("2015-03-09T00:00:00Z")
}
}
]
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"visit_dt" : 1,
"username" : 1
},
"indexName" : "visit_dt_1_username_1",
"isMultiKey" : false,
"direction" : "forward",
"indexBounds" : {
"visit_dt" : [
"[new Date(1425859200000), new Date(1426032000000))"
],
"username" : [
"[MinKey, MaxKey]"
]
}
}
},
"rejectedPlans" : [
...
{
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"visit_dt" : 1,
"file" : 1
},
"indexName" : "visit_dt_1_file_1",
"isMultiKey" : false,
"direction" : "forward",
"indexBounds" : {
"visit_dt" : [
"[new Date(1425859200000), new Date(1426032000000))"
],
"file" : [
"[MinKey, MaxKey]"
]
}
}
},
...
]
}
}
},
{
"$project" : {
"_id" : false,
"file" : true
}
},
{
"$group" : {
"_id" : "$file",
"count" : {
"$sum" : {
"$const" : 1
}
}
}
},
{
"$sort" : {
"sortKey" : {
"count" : -1
}
}
}
],
"ok" : 1
}
答案 0 :(得分:1)
您可能需要阅读the docs regarding $sort
performance:
$ sort运算符在放置在管道的开头或放在$ project,$ unwind和$ group聚合运算符之前时可以利用索引。如果在$ sort操作之前发生$ project,$ unwind或$ group,则$ sort不能使用任何索引。
另外,请记住,出于某种原因,它被称为“聚合管道”。匹配后排序的位置无关紧要。所以解决方案应该非常简单:
db.access_log.aggregate([
{
"$match": {
"visit_dt": {
"$gte": ISODate('2015-03-09'),
"$lt": ISODate('2015-03-11')
},
"file": {"$exists": true }
}
},
{ "$sort": { "file": 1 } },
{ "$project": { "file": 1, "_id": 0 } },
{ "$group": { "_id": "$file", "count": { "$sum": 1 } } },
{ "$sort": { "count": -1 } }
])
当保证每个记录中都存在该字段时,可能不需要检查文件字段是否存在。这并没有伤害,因为该领域有一个索引。与附加排序相同:因为我们确保只有包含文件字段的文档进入管道,所以应该使用索引。
答案 1 :(得分:0)
您可以找到一些信息here
如果查询计划程序选择索引,则说明结果包括a IXSCAN阶段。该阶段包括诸如索引键之类的信息 模式,遍历方向和索引边界。
您的explain()
输出表明发生了IXSCAN,因此您的索引似乎正在按预期工作。
尝试运行aggregate命令而不进行排序或分组,您很可能会看到更好的结果 - 如果是这样,您可以将问题缩小到这些操作中的任何一个。
如果不是这种情况,您还应该在运行此查询时尝试监视系统内存。最有可能发生的事情是Mongo无法在内存中保留40,000,000条记录的索引,因此在运行查询时它会从磁盘交换索引数据(非常慢)。
答案 2 :(得分:0)
感谢@Markus W Mahlberg。
我将查询更改为:
db.access_log.aggregate([
{
"$match": {
"visit_dt": {
"$gte": ISODate('2015-03-09'),
"$lt": ISODate('2015-03-11')
},
}
},
{ "$sort": { "visit_dt": 1, "file": 1 } },
{ "$project": { "file": 1, "_id": 0 } },
{ "$group": { "_id": "$file", "count": { "$sum": 1 } } },
{ "$sort": { "count": -1 } }
], { explain: true })
然后得到了正确的执行计划:
...
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"visit_dt" : 1,
"file" : 1
},
"indexName" : "visit_dt_1_file_1",
"isMultiKey" : false,
"direction" : "forward",
"indexBounds" : {
"visit_dt" : [
"[new Date(1425859200000), new Date(1426032000000))"
],
"file" : [
"[MinKey, MaxKey]"
]
}
}
},
"rejectedPlans" : [ ]
...
虽然它仍然有点慢,但我认为这只是因为我的CPU,Mem,Disks。
非常感谢你!