一 聚合管道的优化
1.$sort + $skip + $limit顺序优化
如果在执行管道聚合时,如果$sort、$skip、$limit依次出现的话,例如:
{ $sort: { age : -1 } },
{ $skip: 10 },
{ $limit: 5 }
那么实际执行的顺序为:
{ $sort: { age : -1 } },
{ $limit: 15 },
{ $skip: 10 }
$limit会提前到$skip前面去执行。
此时$limit = 优化前$skip+优化前$limit
这样做的好处有两个:1.在经过$limit管道后,管道内的文档数量个数会“提前”减小,这样会节省内存,提高内存利用效率。2.$limit提前后,$sort紧邻$limit这样的话,当进行$sort的时候当得到前“$limit”个文档的时候就会停止。
2. $limit + $skip + $limit + $skip Sequence Optimization
如果聚合管道内反复出现下面的聚合序列:
{ $limit: 100 },
{ $skip: 5 },
{ $limit: 10},
{ $skip: 2 }
首先进行局部优化为:可以按照上面所讲的先将第二个$limit提前:
{ $limit: 100 },
{ $limit: 15},
{ $skip: 5 },
{ $skip: 2 }
进一步优化:两个$limit可以直接取最小值 ,两个$skip可以直接相加:
{ $limit: 15 },
{ $skip: 7 }
3. Projection Optimization
过早的使用$project投影,设置需要使用的字段,去掉不用的字段,可以大大减少内存。除此之外也可以过早使用
我们也应该过早使用$match、$limit、$skip操作符,他们可以提前减少管道内文档数量,减少内存占用,提高聚合效率。
除此之外,$match尽量放到聚合的第一个阶段,如果这样的话$match相当于一个按条件查询的语句,这样的话可以使用索引,加快查询效率。
二 聚合管道的限制
1. 类型限制
在管道内不能操作 Symbol, MinKey, MaxKey, DBRef, Code, CodeWScope类型的数据( 2.4版本解除了对二进制数据的限制).
2. 结果大小限制
管道线的输出结果不能超过BSON 文档的大小(16M),如果超出的话会产生错误.
3. 内存限制
如果一个管道操作符在执行的过程中所占有的内存超过系统内存容量的10%的时候,会产生一个错误。
当$sort和$group操作符执行的时候,整个输入都会被加载到内存中,如果这些占有内存超过系统内存的%5的时候,会将一个warning记录到日志文件。同样,所占有的内存超过系统内存容量的10%的时候,会产生一个错误。
三 分片上使用聚合管道
聚合管道支持在已分片的集合上进行聚合操作。当分片集合上进行聚合操作的时候,聚合管道被分为两成两个部分,分别在mongod实例和mongos上进行操作。
四 聚合管道使用
首先下载测试数据:http://media.mongodb.org/zips.json 并导入到数据库中。
1. 查询人口树大于一千万的州的名字
> db.zipcode.aggregate({$group:{_id:"$state",totalPop:{$sum:"$pop"}}},
{$match:{totalPop:{$gte:10*1000*1000}}},
{$sort:{_id:1}})
{
"result" : [
{
"_id" : "CA",
"totalPop" : 29760021
},
{
"_id" : "FL",
"totalPop" : 12937926
},
{
"_id" : "IL",
"totalPop" : 11430602
},
{
"_id" : "NY",
"totalPop" : 17990455
},
{
"_id" : "OH",
"totalPop" : 10847115
},
{
"_id" : "PA",
"totalPop" : 11881643
},
{
"_id" : "TX",
"totalPop" : 16986510
}
],
"ok" : 1
}
2. 计算每个州平均每个城市打人口数
> db.zipcode.aggregate({$group:{_id:{state:"$state",city:"$city"},pop:{$sum:"$pop"}}},
{$group:{_id:"$_id.state",avCityPop:{$avg:"$pop"}}},
{$sort:{_id:1}})
{
"result" : [
{
"_id" : "AK",
"avCityPop" : 2989.3641304347825
},
{
"_id" : "AL",
"avCityPop" : 7907.2152641878665
},
{
"_id" : "AR",
"avCityPop" : 4175.355239786856
},
{
"_id" : "AZ",
"avCityPop" : 20591.16853932584
},
{
"_id" : "CA",
"avCityPop" : 27581.113067655235
},
{
"_id" : "CO",
"avCityPop" : 9922.873493975903
},
{
"_id" : "CT",
"avCityPop" : 14674.625
},
{
"_id" : "DC",
"avCityPop" : 303450
},
{
"_id" : "DE",
"avCityPop" : 14481.91304347826
},
{
"_id" : "FL",
"avCityPop" : 26676.136082474226
},
………………
………………
………………
{
"_id" : "WI",
"avCityPop" : 7323.00748502994
},
{
"_id" : "WV",
"avCityPop" : 2759.1953846153847
},
{
"_id" : "WY",
"avCityPop" : 3359.911111111111
}
],
"ok" : 1
}
3. 计算每个州人口最多和最少的城市名字
>db.zipcode.aggregate({$group:{_id:{state:"$state",city:"$city"},pop:{$sum:"$pop"}}},
{$sort:{pop:1}},
{$group:{_id:"$_id.state",biggestCity:{$last:"$_id.city"},biggestPop:{$last:"$pop"},smallestCity:{$first:"$_id.city"},smallestPop:{$first:"$pop"}}},
{$project:{_id:0,state:"$_id",biggestCity:{name:"$biggestCity",pop:"$biggestPop"},smallestCity:{name:"$smallestCity",pop:"$smallestPop"}}})
{
"result" : [
"biggestCity" : {
"name" : "GRAND FORKS",
"pop" : 59527
},
"smallestCity" : {
"name" : "TROTTERS",
"pop" : 12
},
"state" : "ND"
},
…………………
…………………
………………...
{
"biggestCity" : {
"name" : "WASHINGTON",
"pop" : 606879
},
"smallestCity" : {
"name" : "PENTAGON",
"pop" : 21
},
"state" : "DC"
},
{
"biggestCity" : {
"name" : "DENVER",
"pop" : 451182
},
"smallestCity" : {
"name" : "CHEYENNE MTN AFB",
"pop" : 0
},
"state" : "CO"
}
],
"ok" : 1
}
五 总结
对于大多数的聚合操作,聚合管道可以提供很好的性能和一致的接口,使用起来比较简单, 和MapReduce一样,它也可以作用于分片集合,但是输出的结果只能保留在一个文档中,要遵守BSON Document大小限制(当前是16M)。
管道对数据的类型和结果的大小会有一些限制,对于一些简单固定的聚集操作可以使用管道,但是对于一些复杂的、大量数据集的聚合任务还是使用MapReduce。