我在 mongoDB 中有以下示例文档。
{
"location" : {
"language" : null,
"country" : "null",
"city" : "null",
"state" : null,
"continent" : "null",
"latitude" : "null",
"longitude" : "null"
},
"request" : [
{
"referrer" : "direct",
"url" : "http://www.google.com/"
"title" : "index page"
"currentVisit" : "1401282897"
"visitedTime" : "1401282905"
},
{
"referrer" : "direct",
"url" : "http://www.stackoverflow.com/",
"title" : "index page"
"currentVisit" : "1401282900"
"visitedTime" : "1401282905"
},
......
]
"uuid" : "109eeee0-e66a-11e3"
}
备注:
- 数据库包含超过
10845
个文件 - 每个文档包含近
100
个请求(请求数组中有100个对象)。 技术/语言 -
node.js
我有
setProfiling
来检查执行时间First Query - 13899ms Second Query - 9024ms Third Query - 8310ms Fourth Query - 6858ms
使用索引没有太大区别
查询:
我要执行以下聚合查询
以获取数据。
var match = {"request.currentVisit":{$gte:core.getTime()[1].toString(),$lte:core.getTime()[0].toString()}};
例如:
var match = {"request.currentVisit":{$gte:"1401282905",$lte:"1401282935"}};
对于第三和第四次查询request.visitedTime
而不是request.currentVisit
首先
[ { "$project":{ "request.currentVisit":1, "request.url":1 }}, { "$match":{ "request.1": {$exists:true} }}, { "$unwind": "$request" }, { "$match": match }, { "$group": { "_id": { "url":"$request.url" }, "count": { "$sum": 1 } }}, { "$sort":{ "count": -1 } } ]
第二个
[ { "$project": { "request.currentVisit":1, "request.url":1 }}, { "$match": { "request":{ "$size": 1 } }}, { "$unwind": "$request" }, { "$match": match }, { "$group": { "_id":{ "url":"$request.url" }, "count":{ "$sum": 1 } }}, { "$sort": { "count": -1} } ]
第三
[ { "$project": { "request.visitedTime":1, "uuid":1 }}, { "$match":{ "request.1": { "$exists": true } }}, { "$match": match }, { "$group": { "_id": "$uuid", "count":{ "$sum": 1 } }}, { "$group": { "_id": null, "total": { "$sum":"$count" }} }} ]
第四
[ { "$project": { "request.visitedTime":1, "uuid":1 }}, { "$match":{ "request":{ "$size": 1 } }}, { "$match": match }, { "$group": { "_id":"$uuid", "count":{ "$sum": 1 } }}, { "$group": { "_id":null, "total": { "$sum": "$count" } }} ]
问题:
获取数据花费的时间超过 38091 毫秒
。
有什么办法可以优化查询吗?
任何建议将不胜感激。
最佳答案
好吧,有一些问题,你肯定需要索引,但你不能有复合索引。它是您在要索引的数组中查询的“时间戳”值。还建议您将它们转换为数值而不是当前字符串,或者实际上转换为 BSON 日期类型。后一种形式实际上在内部存储为数字时间戳值,因此可以减少一般的存储大小,这也减少了索引大小,并且可以更有效地匹配数值。
每个查询的最大问题是您总是在处理完 $unwind
之后再深入“数组”内容。然后用匹配“过滤”它。虽然这是您想对结果执行的操作,但由于您没有在较早阶段应用相同的过滤器,因此当您 $unwind
时,管道中有许多不符合这些条件的文档。 .结果是您不需要在此阶段处理的“大量”文档。在这里你不能使用索引。
您需要此匹配的地方是管道阶段的开始。这会在过滤实际数组之前将文档缩小到“可能的”匹配项。
以第一个为例:
[
{ "$match":{
{ "request.currentVisit":{
"$gte":"1401282905", "$lte": "1401282935"
}
}},
{ "$unwind": "$request" },
{ "$match":{
{ "request.currentVisit":{
"$gte":"1401282905", "$lte": "1401282935"
}
}},
{ "$group": {
"_id": {
"url":"$request.url"
},
"count": { "$sum": 1 }
}},
{ "$sort":{ "count": -1 } }
]
所以有一些变化。有一个 $match
在管道的头部。这会缩小文档范围并能够使用索引。这是最重要的性能考虑因素。黄金法则,始终首先“匹配”。
$project
你在那里是多余的,因为你不能“只”转换一个尚未展开的数组的字段。还有一种误解,认为人们相信他们$project
首先要减少流水线。如果实际上有一个稍后的 $project
,则效果非常小或 $group
实际上限制字段的语句,那么这将是“前向优化”的,所以事情确实会为您从管道处理中取出。还是$match
上面的语句做了更多的优化。
不再需要查看数组是否真的存在于另一个 $match
中阶段,因为您现在在管道开始时“隐式”执行此操作。如果更多条件让您感到更舒服,则将它们添加到初始管道阶段。
其余不变,随你便$unwind
数组和 $match
在继续进行剩余处理之前过滤您真正想要的项目。到目前为止,输入文档已经显着减少,或者尽可能减少。
您可以使用 MongoDB 2.6 和更高版本做的另一种选择是在您甚至 **$unwind
之前“过滤”数组内容。它。这将产生如下列表:
[
{ "$match":{
{ "request.currentVisit":{
"$gte":"1401282905", "$lte": "1401282935"
}
}},
{ "$project": {
"request": {
"$setDifference": [
{
"$map": {
"input": "$request",
"as": "el",
"in": {
"$cond"": [
{
"$and":[
{ "$gte": [ "1401282905", "$$el.currentVisit" ] },
{ "$lt": [ "1401282935", "$$el.currentVisit" ] }
]
}
"$el",
false
]
}
}
}
[false]
]
}
}}
{ "$unwind": "$request" },
{ "$group": {
"_id": {
"url":"$request.url"
},
"count": { "$sum": 1 }
}},
{ "$sort":{ "count": -1 } }
]
这可以通过在 $unwind
之前“过滤”数组来为您节省一些费用这可能比做 $match
更好之后。
但这是您所有陈述的一般规则。您需要可用的索引并且需要 $match
首先。
有可能您真正想要的实际结果可以在单个查询中获得,但就目前而言,您的问题并未以这种方式呈现。尝试按照概述更改您的处理,您应该会看到显着的改进。
如果您还在尝试接受这怎么可能是单一的,那么您可以随时提出另一个问题。
关于mongodb - 如何优化 mongoDB 查询?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24205067/