【问题标题】:Aggregate and select only top records with $last使用 $last 聚合并仅选择顶部记录
【发布时间】:2014-01-15 08:55:45
【问题描述】:

我在 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 都被索引。如果我在 group 之前添加 { $sort : { date : 1 } } 它不会改变任何东西。

基本上,我的查询结果不应该取决于数据大小。我唯一需要的是每个设备/资产的最高记录。如果我改为执行 80 个单独的查询,则需要几毫秒。

有没有办法让 MongoDB 不遍历整个表?看起来数据库并没有减少而是处理所有内容?!好吧,我知道这种行为应该有充分的理由,但我在文档或论坛上找不到任何东西。

更新:

最终找到了 2.4.6 的解释查询的正确语法:

db.runCommand( { aggregate: "collection", pipeline : [...] , explain : true })

结果:

{ “服务器管道”:[ { “询问” : { “资产ID”:{ “$in”:[ "52744d5722f8cb9b4f94d321", "52791fe322f8014b320dae41", "52740f5222f8cb9b4f94d306", ...由于 SO 限制,必须删除一些 "52744d1722f8cb9b4f94d31d", "52744b1d22f8cb9b4f94d308", “52744ccd22f8cb9b4f94d319” ] } }, “投影”:{ “资产ID”:1, “日期”:1, “_id”:0 }, “光标”:{ "cursor" : "BtreeCursor assetId_1 multi", “isMultiKey”:假, “n”:960881, “nscannedObjects”:960881, “nscanned”:960894, “nscannedObjectsAllPlans”:960881, “nscannedAllPlans”:960894, “scanAndOrder”:假, “indexOnly”:假, “nYields”:9, “nChunkSkips”:0, “毫”:6264, “索引边界”:{ “资产 ID”:[ [ "52740baa22f8cb9b4f94d2e8", “52740baa22f8cb9b4f94d2e8” ], [ "52740bed22f8cb9b4f94d2e9", “52740bed22f8cb9b4f94d2e9” ], [ “52740c3222f8cb9b4f94d2ea”, “52740c3222f8cb9b4f94d2ea” ], …… [ "5297770a22f82f9bdafce322", “5297770a22f82f9bdafce322” ], [ "529df5f622f82f9bdafce429", “529df5f622f82f9bdafce429” ], [ "529f6a6722f89deaabbf9881", “529f6a6722f89deaabbf9881” ], [ "52a6e35122f89ce6e2cf4267", “52a6e35122f89ce6e2cf4267” ] ] }, “所有计划”:[ { "cursor" : "BtreeCursor assetId_1 multi", “n”:960881, “nscannedObjects”:960881, “nscanned”:960894, “索引边界”:{ “资产 ID”:[ [ "52740baa22f8cb9b4f94d2e8", “52740baa22f8cb9b4f94d2e8” ], [ "52740bed22f8cb9b4f94d2e9", “52740bed22f8cb9b4f94d2e9” ], [ "52740c3222f8cb9b4f94d2ea", “52740c3222f8cb9b4f94d2ea” ], ………… [ "529df5f622f82f9bdafce429", “529df5f622f82f9bdafce429” ], [ "529f6a6722f89deaabbf9881", “529f6a6722f89deaabbf9881” ], [ "52a6e35122f89ce6e2cf4267", “52a6e35122f89ce6e2cf4267” ] ] } } ], “旧计划”:{ "cursor" : "BtreeCursorassetId_1 multi", “索引边界”:{ “资产 ID”:[ [ "52740baa22f8cb9b4f94d2e8", “52740baa22f8cb9b4f94d2e8” ], [ "52740bed22f8cb9b4f94d2e9", “52740bed22f8cb9b4f94d2e9” ], [ “52740c3222f8cb9b4f94d2ea”, “52740c3222f8cb9b4f94d2ea” ], ........... [ "529df5f622f82f9bdafce429", “529df5f622f82f9bdafce429” ], [ "529f6a6722f89deaabbf9881", “529f6a6722f89deaabbf9881” ], [ "52a6e35122f89ce6e2cf4267", “52a6e35122f89ce6e2cf4267” ] ] } }, “服务器”:“351bcc56-1a25-61b7-a435-c14e06887015.local:27017” } }, { “$组”:{ "_id" : "$assetId", “日期” : { "$last" : "$date" } } } ], “好”:1 }

【问题讨论】:

  • 我认为这里发生的事情是 mongodb 在 deviceId 上使用了索引。每个查询只能使用一个索引,因此不能使用日期索引。你能发布解释的输出吗?
  • 用 explain() 输出更新了问题。如果 MongoDB 真的不能在聚合查询中使用多个索引,那么它解释了性能问题。
  • 这个集合的索引是什么?您提到“asset_id”和“date”都已编入索引 - 但它们需要在同一个索引中才能获得最大收益。除非查询包含 $or 子句,否则 MongoDB 2.4 将仅对每个查询使用单个索引。此外,虽然您只要求 $last 日期值,但聚合查询仍必须迭代所有匹配值才能找到每个组的最后一个值。当前查询的结果不取决于数据大小,但查询的执行时间会。
  • 我会尝试:在{assetId:1, date:-1} 上添加索引;在$match之后添加{$sort: { 'date': -1}}操作;每组使用$first 匹配而不是$last
  • 非常感谢@Stennie,您的 cmets 对找出问题所在非常有帮助。我认为应该在汇总文档中提到这一点,大阅读通知!我刚刚在docs.mongodb.org/manual/core/aggregation-pipeline 中找到了类似的示例。实际上,我对该集合几乎没有不同的查询,并且每次将assetId 和 date 一起使用。所以这是更多的设计错误。我将创建复合索引并删除assetId 和日期索引。

标签: mongodb


【解决方案1】:

我不确定,但如果您在 monngodb 网站上阅读此 link

注意 仅当 $group 遵循 $sort 操作时才使用 $last。否则,此操作的结果是不可预测的。

【讨论】:

    【解决方案2】:

    我的程序也有同样的问题。我已经尝试过 mongoDB MapReduce、聚合框架等,但最后我停止使用索引扫描集合并在客户端形成结果。但是现在集合太大了,无法做到这一点,所以我想我会使用很多小查询,正如您在上面的问题中提到的那样。它不是那么漂亮,但恕我直言,它将是最快的解决方案。

    只有管道中的第一个查询使用索引。管道中的第二个查询接受第一个查询的输出,它很大并且没有索引。但正如Pipeline Operators and Indexes 中提到的那样,您的查询可以使用复合索引,因此不太清楚。

    我有一个想法:您可以尝试使用多个 $or 运算符,而不是像这样的一个 $in 运算符 { "$match": { "$or": [{"assetId": <id1>}, {"assetId": <id2>...}] } }。据我所知$or 运算符可以并行执行,每个查询都可以使用索引。所以测试这个解决方案会很有趣。

    附言如果能找到解决这个问题的方法,我真的会很高兴。

    【讨论】:

    • $ 或绝对不起作用。在 $in nscanned=673967 的情况下,对于 $ 或 nscanned=14458579,例如,整个 db。而且几乎需要一分钟。
    • @ruruskyi,奇怪.. 我已经在大约 20 GB 的集合上尝试过它,当使用 $in 和 $ 或 nscanned 时是相等的。但是第一次执行很长可能是因为索引不在内存中。其他电话大致相同。附言我越来越相信聚合框架只适合离线操作……
    【解决方案3】:

    您的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)
    

    【讨论】:

    • 是的,我们目前为每个资产发出一个命令。谢谢指点票。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-15
    • 1970-01-01
    • 2013-06-14
    • 2013-03-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多