MongoDB,用bigdata慢查询
我试图在mongodb的大集合上执行一个查询,实际上查询是由两部分组成,总共需要执行大约900ms,我需要它快得多。
这些是收集, 停止时间 :
> db.stoptimes.find().limit(1); { "trip_id": "24893A459B661", "arrival_time": "22:30:00", "departure_time": "22:30:00", "stop_id": "1904", "stop_sequence": 2, "stop_headsign": "", "pickup_type": "0", "drop_off_type": "0", "shape_dist_traveled": "0.88659123054", "agency_key": "alamedaoakland-ferry", "_id": ObjectId("52b394c680052ea30918fd62") } > db.stoptimes.count(); 5959551
和旅行 :
> db.trips.find().limit(1); { "route_id": "60", "service_id": "180A536", "trip_id": "23736A180B536", "trip_short_name": "", "trip_headsign": "San Francisco via Pier 41", "direction_id": "", "block_id": "282", "shape_id": "30", "trip_bikes_allowed": "2", "agency_key": "alamedaoakland-ferry", "_id": ObjectId("52b394c780052ea30918ff34") } > db.trips.count(); 204884
我试图find每个不同的route_id旅行收集里面trip_id等于每个旅行id匹配停止时间给定的stop_id。
------ stoptimes --- -> ---------- trips ----------------- stop_id1 -> trip_id1 -> trip_id1 -> route_id1 -> route_id1 -> trip_id2 -> trip_id2 -> route_id2 -> route_id2 -> trip_id3 -> trip_id3 -> route_id2 -> trip_id4 -> trip_id4 -> route_id2 -> trip_id5 -> trip_id5 -> route_id3 -> route_id3
这是在mongodb shell中的查询:
> var tripids = db.stoptimes.aggregate([ ... {$match : { 'stop_id' : '1904' }}, ... {$project : { '_id' : 0, 'trip_id' : 1 }} ... ]); > var arr = []; > for(var i=0; i<tripids.result.length; i++) ... { arr.push(tripids.result[i].trip_id); } > db.trips.aggregate([ ... {$match : { 'trip_id' : {$in : arr}}}, ... {$group : { ... _id : "$route_id", ... direction_id : { $first: '$direction_id'}, ... shape_id : {$first : '$shape_id'}}} ... ])
这是我使用的JavaScript的一块,请注意它是node.js + mongoose,但它应该很容易阅读,因为它是纯javascript:
StopTime .aggregate([ {$match : { 'stop_id' : stop_id }}, {$project : { '_id' : 0, 'trip_id' : 1 }} ], function (err, trip_ids){ var arr = []; for(var i=0;i<trip_ids.length;i++) { arr.push(trip_ids[i].trip_id); } Trip .aggregate([ {$match : { 'trip_id' : {$in : arr} }}, {$group : { _id : "$route_id", direction_id : { $first: '$direction_id'}, shape_id : { $first: '$shape_id'} }} ], function (err, route_ids){ cb(err, route_ids); }); });
我能做些什么来改善表演?
编辑:
这是唯一需要这么长时间的查询:
> db.trips.aggregate([ ... {$match : { 'trip_id' : {$in : arr}}}, ... {$group : { ... _id : "$route_id", ... direction_id : { $first: '$direction_id'}, ... shape_id : {$first : '$shape_id'}}} ... ])
这看起来像是在所有行程(204884行程)上运行匹配数组中任何logging的聚合方法。 如果这是真的,你正在处理约228logging/毫秒,这是相当不错的。
你可以在你的代码中做一些明显的优化
除非你有特定的原因,否则不要使用i ++,总是把它写成++ i,并把你的计数放在单独的variables中
var trip_ids_length = trip_ids.length; for(var i=0;i<trip_ids_length;++i) { arr.push(trip_ids[i].trip_id); }
你的trip_id是一个相当复杂的string, 即24893A459B661 ,string比较总是慢于整数比较。 此外,比赛将不得不为每个将testing的比赛拉动指定的json行。
一些选项
- 重新考虑你的对象的停工时间和旅行,最好的捷径是用一个整数值代替trip_id
- 创build一个索引列表,其中所有的trip_id更小更快地运行匹配,您应该将关联对象的INDEX存储在跳闸和/或停止时间 ; 即t_index和s_index
- 创build一个Web服务configuration为在静态内存中保存旅程和停止时间,并在那里进行匹配
我个人的观点是,MongoDB和类似的引擎还没有处理这种types的操作相比,一个常规的关系数据库引擎,如SQL Server,MySQL,PostgreSQL 。
确保在“旅行”集合中有“trip_id”的索引。 即使使用索引,如果您提供了“arr”的一长串值,您将无法获得最佳性能。 '$ in'操作符很难优化,因为必须查看每个值。 例如,如果'arr'数组有10个值,那么每个值都必须search索引。 它基本上看起来像10个子查询。
您可以devise您的模式,以避免使用'$ in'操作符,查找2个集合并使用聚合框架。
我将假设“trip_id + stop_id”在“停车时间”集合中是唯一的,而“route_id”在“旅行”集合中是唯一的。
让我们对数据进行非规范化。 保留“停止时间”集合以携带停靠点的详细信息,但是我们将这些信息添加到“旅行”集合中:
{ "route_id": "60", "service_id": "180A536", "trip_id": "23736A180B536", "stop_id" : [ 1800, 1830, 1904] <============== "trip_short_name": "", "trip_headsign": "San Francisco via Pier 41", "direction_id": "", "block_id": "282", "shape_id": "30", "trip_bikes_allowed": "2", "agency_key": "alamedaoakland-ferry", "_id": ObjectId("52b394c780052ea30918ff34") }
然后你的查询变成:
db.trips.find({"stop_id":1904}, {"_id":0, "route_id":1, "direction_id":1, "shape_id":1})
用“stop_id”索引,你的查询应该是非常快的。
总之,devise您的模式,以便针对最重要的查询进行优化。 如果上述查询是最重要的,那么您将通过新的模式devise获利。 如果这是一个孤立的查询,并且已经针对您的常见情况进行了优化,那么Eric的build议可能只是做您所需要的。 如果您决定保留聚合框架解决scheme,则可以评估聚合pipe道第一步的性能。 运行以下命令以确保$匹配步骤正在使用索引。
db.collection.runCommand("aggregate", {pipeline: YOUR_PIPELINE, explain: true})