我在MongoDB中有以下集合:
{
"_id" : ObjectId("..."),
"assetId" : "...",
"date" : ISODate("..."),
...
}
我需要做很简单的事情 - 找到每个设备/资产的最新记录。我有以下查询:
db.collection.aggregate([
{ "$match" : { "assetId" : { "$in" : [ up_to_80_ids ]} } },
{ "$group" :{ "_id" : "$assetId" , "date" : { "$last" : "$date"}}}
])
整张桌子大概是20Gb。当我尝试执行此查询时,它需要大约8秒,这没有任何意义,只要我指定只应选择$ last记录。 assetId和date都被编入索引。如果我在群组之前添加 {$ sort:{date:1}} ,则不会更改任何内容。
基本上,我的查询结果不应该依赖于数据大小。我唯一需要的是每个设备/资产的最高记录。如果我做80个单独的查询,我需要几毫秒。
有没有办法让MongoDB不通过整个表?它看起来像数据库不会减少,但处理一切?!好吧,我知道这种行为应该有一些很好的理由,但我在文档或论坛上找不到任何内容。
更新:
最终找到了解释查询的正确语法2.4.6:
db.runCommand( { aggregate: "collection", pipeline : [...] , explain : true })
结果:
{ "serverPipeline" : [ { "query" : { "assetId" : { "$in" : [ "52744d5722f8cb9b4f94d321", "52791fe322f8014b320dae41", "52740f5222f8cb9b4f94d306", ... must remove some because of SO limitations "52744d1722f8cb9b4f94d31d", "52744b1d22f8cb9b4f94d308", "52744ccd22f8cb9b4f94d319" ] } }, "projection" : { "assetId" : 1, "date" : 1, "_id" : 0 }, "cursor" : { "cursor" : "BtreeCursor assetId_1 multi", "isMultiKey" : false, "n" : 960881, "nscannedObjects" : 960881, "nscanned" : 960894, "nscannedObjectsAllPlans" : 960881, "nscannedAllPlans" : 960894, "scanAndOrder" : false, "indexOnly" : false, "nYields" : 9, "nChunkSkips" : 0, "millis" : 6264, "indexBounds" : { "assetId" : [ [ "52740baa22f8cb9b4f94d2e8", "52740baa22f8cb9b4f94d2e8" ], [ "52740bed22f8cb9b4f94d2e9", "52740bed22f8cb9b4f94d2e9" ], [ "52740c3222f8cb9b4f94d2ea", "52740c3222f8cb9b4f94d2ea" ], .... [ "5297770a22f82f9bdafce322", "5297770a22f82f9bdafce322" ], [ "529df5f622f82f9bdafce429", "529df5f622f82f9bdafce429" ], [ "529f6a6722f89deaabbf9881", "529f6a6722f89deaabbf9881" ], [ "52a6e35122f89ce6e2cf4267", "52a6e35122f89ce6e2cf4267" ] ] }, "allPlans" : [ { "cursor" : "BtreeCursor assetId_1 multi", "n" : 960881, "nscannedObjects" : 960881, "nscanned" : 960894, "indexBounds" : { "assetId" : [ [ "52740baa22f8cb9b4f94d2e8", "52740baa22f8cb9b4f94d2e8" ], [ "52740bed22f8cb9b4f94d2e9", "52740bed22f8cb9b4f94d2e9" ], [ "52740c3222f8cb9b4f94d2ea", "52740c3222f8cb9b4f94d2ea" ], ....... [ "529df5f622f82f9bdafce429", "529df5f622f82f9bdafce429" ], [ "529f6a6722f89deaabbf9881", "529f6a6722f89deaabbf9881" ], [ "52a6e35122f89ce6e2cf4267", "52a6e35122f89ce6e2cf4267" ] ] } } ], "oldPlan" : { "cursor" : "BtreeCursor assetId_1 multi", "indexBounds" : { "assetId" : [ [ "52740baa22f8cb9b4f94d2e8", "52740baa22f8cb9b4f94d2e8" ], [ "52740bed22f8cb9b4f94d2e9", "52740bed22f8cb9b4f94d2e9" ], [ "52740c3222f8cb9b4f94d2ea", "52740c3222f8cb9b4f94d2ea" ], ........ [ "529df5f622f82f9bdafce429", "529df5f622f82f9bdafce429" ], [ "529f6a6722f89deaabbf9881", "529f6a6722f89deaabbf9881" ], [ "52a6e35122f89ce6e2cf4267", "52a6e35122f89ce6e2cf4267" ] ] } }, "server" : "351bcc56-1a25-61b7-a435-c14e06887015.local:27017" } }, { "$group" : { "_id" : "$assetId", "date" : { "$last" : "$date" } } } ], "ok" : 1 }
答案 0 :(得分:2)
您的explain
输出表明您的$match
阶段有960,881项与assetIds相匹配。 MongoDB使用assetId
上的索引查找所有索引,并通过$group
阶段将它们全部流式传输。这很贵。目前,MongoDB没有对聚合管道进行很多全流程优化,所以你写的就是你得到的,非常多。
MongoDB可以通过assetId升序和日期降序排序来优化此管道,然后应用SERVER-9507中建议的优化,但尚未实现。
目前,您最好的做法是为每个assetId执行此操作:
db.collection.find({assetId: THE_ID}).sort({date: -1}).limit(1)
答案 1 :(得分:0)
我不确定,但如果您在monngodb网站上阅读此link。
它已注意 仅当$ group跟随$ sort操作时才使用$ last。否则,此操作的结果是不可预测的。
答案 2 :(得分:0)
我的程序遇到同样的问题。我已经尝试了mongoDB MapReduce,聚合框架等,但最后我停止使用索引扫描集合并在客户端上形成结果。但是现在收藏太大了,所以我想我会在你的问题中使用上面提到的许多小查询。它不是那么美丽,但它将是最快的解决方案恕我直言。
只有管道中的第一个查询使用索引。管道中的第二个查询接受第一个查询的输出,它很大而且没有索引。但正如Pipeline Operators and Indexes中提到的,您的查询可以使用复合索引,因此不太清楚。
我有一个想法:您可以尝试使用多个$or
运算符而不是一个$in
运算符
{ "$match": { "$or": [{"assetId": <id1>}, {"assetId": <id2>...}] } }
。据我所知,$or
运算符可以并行执行,每个查询都可以使用索引。所以测试这个解决方案会很有趣。
P.S。如果找到这个问题的解决方案,我真的很高兴。