我有一个30mb大小的数据库,它有300个文件存储在一个集合中,它们的大小从1mb到10kb不等。我正在使用 2.6 附带的新聚合框架,我没有任何索引。
我有一个汇总管道如下:
1. $match > first query match
2. $project > exclude some fields for efficiency
3. $unwind > unwind one of the arrays
4. $unwind > unwind second array
5. $project > projection to find matching fields among two arrays with $eq
6. $match > same:true
7. $group > put the unwinded arrays together
8. $limit(50)
以上此管道需要 30 秒。 如果我删除$ limit,则需要很长时间。我的问题是:
数据库大小仅 30MB ,管道根本不复杂。为什么需要这么长时间?有什么想法?
修改
我的架构如下:
{
username: string (max 20 chars
userid : string max 20 chars
userage : string max 20 chars
userobj1: array of objects, length: ~300-500
// example of userobj1:
[
{
innerobj1: array of objects, length: ~30-50
innerobj2: array of objects, length: ~100-200
userinfo1: string max 20 chars
userinfo2: string max 20 chars
userinfo3: string max 20 chars
userinfo4: string max 20 chars
} ...
]
userobj2: same as userobj1
userobj3: same as userobj1
userobj4: same as userobj1
}
上面的这个文件有多达3-4级的内部对象。抱歉,我无法提供示例,但别名应该足够了。示例查询如下:
1. $match:
$and : [
{userobj1: $elemMatch: {userinfo1:a}},
{userobj1: $elemMatch: {userinfo4:b}}
]
2. $project {username:1, userid:1, userobj1:1, userobj2:1}
3. $unwind userobj1
4. $unwind userobj2
5. $project
{
username:1,
userid:1,
userobj1:1,
userobj2:1,
userobj3:1,
userobj4:1,
"same" : {
$eq: [ userobj3.userinfo4, userobj4.userinfo4 ]
}
}
6. $match {same:true}
7. $group all arrays back
8. limit 50.
答案 0 :(得分:2)
这里有些东西我不知道你在这里想要做什么。所以请关注我看到的可能的实际问题和答案。
考虑到这种简化的数据集:
{
"obj1": [
{ "a": "a", "b": "b" },
{ "a": "a", "b": "c" }
],
"obj2": [
{ "a": "c", "b": "b" },
{ "a": "c", "b": "c" }
]
},
{
"obj1": [
{ "a": "a", "b": "b" }
],
"obj2": [
{ "a": "a", "b": "c" }
]
}
问: "您是不是只想将文档与{ "a": "a", "b": b" }
匹配在" obj1"还有{ "b": "b" }
" object2"?"
如果是这种情况,那么这只是一个.find()
的简单查询:
db.collection.find({
"obj1": {
"$elemMatch": { "a": "a", "b": "b" }
},
"obj2.b": "b"
})
只匹配满足条件的其中一个文档,在这种情况下仅匹配一个:
{
"obj1": [
{ "a": "a", "b": "b" },
{ "a": "a", "b": "c" }
],
"obj2": [
{ "a": "c", "b": "b" },
{ "a": "c", "b": "c" }
]
}
问: "您是否可能尝试在条件为true
的数组中查找位置?"
如果是这样,MongoDB 2.6可以使用一些操作符来帮助您而不使用$unwind
:
db.objects.aggregate([
{ "$match": {
"obj1": {
"$elemMatch": { "a": "a", "b": "b" }
},
"obj2.b": "b"
}},
{ "$project": {
"obj1": 1,
"obj2": 1,
"match1": {
"$map": {
"input": "$obj1",
"as": "el",
"in": {
"$and": [
{ "$eq": [ "$$el.a", "a" ] },
{ "$eq": [ "$$el.b", "b" ] }
]
}
}
},
"match2": {
"$map": {
"input": "$obj2",
"as": "el",
"in": {
"$eq": [ "$$el.b", "b" ]
}
}
}
}}
])
给你:
{
"obj1": [
{ "a": "a", "b": "b" },
{ "a": "a", "b": "c" }
],
"obj2": [
{ "a": "c", "b": "b" },
{ "a": "c", "b": "c" }
],
"match1" : [
true,
false
],
"match2" : [
true,
false
]
}
问: "或者您可能正在尝试"过滤"只有符合这些条件的匹配数组元素?"
您可以使用MongoDB 2.6中的更多集合运算符执行此操作,而无需使用$unwind
:
db.objects.aggregate([
{ "$match": {
"obj1": {
"$elemMatch": { "a": "a", "b": "b" }
},
"obj2.b": "b"
}},
{ "$project": {
"obj1": {
"$setDifference": [
{ "$map": {
"input": "$obj1",
"as": "el",
"in": {
"$cond": [
{ "$and": [
{ "$eq": [ "$$el.a", "a" ] },
{ "$eq": [ "$$el.b", "b" ] }
]},
"$$el",
false
]
}
}},
[false]
]
},
"obj2": {
"$setDifference": [
{ "$map": {
"input": "$obj2",
"as": "el",
"in": {
"$cond": [
{ "$eq": [ "$$el.b", "b" ] },
"$$el",
false
]
}
}},
[false]
]
}
}}
])
结果:
{
"obj1": [
{ "a": "a", "b": "b" },
],
"obj2": [
{ "a": "c", "b": "b" },
]
}
最后一个条目是最可爱的,它结合了$cond
,$map
和$setDifference
来对数组中的对象进行一些复杂的过滤,以便仅过滤条件的匹配。您以前必须$unwind
和$match
才能获得这些结果。
所以$unwind
和$group
都不需要实际获得任何这些结果,而这些实际上是在杀死你。你的大人物也会通过"在'#34; unwound"带有$eq
的数组建议尝试获得上述其中一个的最终结果,但是你实现它的方式会非常昂贵。
还尝试在其中一个数组中包含一个索引,以使该元素匹配,从而尽可能减少您的工作结果。在所有情况下,即使你不能使用复合物,它也可以改善一些东西。索引由于那里的限制。
无论如何,希望至少有一些符合你意图的东西,或者至少接近你想要的东西。
由于您的评论采用了这种方式,因此匹配" obj1.a"的值到" obj2.b"没有过滤与显示的一般情况没有太大的不同。
db.objects.aggregate([
{ "$project": {
"match": {
"$size": {
"$setIntersection": [
{ "$map": {
"input": "$obj1",
"as": "el",
"in": { "$concat": ["$$el.a",""] }
}},
{ "$map": {
"input": "$obj2",
"as": "el",
"in": { "$concat": ["$$el.b",""] }
}}
]
}
}
}},
{ "$match": { "$gte": 1 } }
])
所有操作都是在不使用$unwind
的情况下完成的。
答案 1 :(得分:1)
我的猜测是需要很长时间,因为没有索引,因此每次需要记录时都会进行完整的集合扫描。
尝试在userinfo1:a
上添加索引,我认为您会看到良好的性能提升。我还建议您从AND
阶段删除match
语法并将其重写为列表。
我认为对你和问题提供聚合解释的输出真的很有帮助。在mongo 2.6中,您可以拥有explain in aggregation pipeline。
db.collection.aggregate( [ ... stages ...], { explain:true } )
答案 2 :(得分:1)
我知道这是一个老问题,但看起来似乎没有达到一个简单的答案,它涉及使用2.6中提供的表达式,因此它也可以使用。您无需执行任何$unwind
或复杂$map
ping操作,只需要在要查找匹配项的两个阵列上执行$setIntersection
。
使用来自很长答案的示例数据:
db.foo.aggregate(
{$match:{"obj1.a":"a"}},
{$project:{keep:{$setIntersection:["$obj1.b","$obj2.b"]},obj1:1,obj2:1}},
{$match:{keep:{$ne:[]}}})
{ "_id" : ObjectId("588a8206c01d80beca3a8e45"), "obj1" : [ { "a" : "a", "b" : "b" }, { "a" : "a", "b" : "c" } ], "obj2" : [ { "a" : "c", "b" : "b" }, { "a" : "c", "b" : "c" } ], "keep" : [ "b", "c" ] }
只保留两个文档中的一个,即在obj1和obj2数组中都有两个“b”值的文档。
在原始的“语法”中,$project
阶段将是
same: {$setIntersection: [ '$userobj3.userinfo4', '$userobj4.userinfo4' ]}