【问题标题】:Poor lookup aggregation performance查找聚合性能差
【发布时间】:2017-09-30 06:10:46
【问题描述】:

我有两个收藏

帖子:

{
    "_Id": "1",
    "_PostTypeId": "1",
    "_AcceptedAnswerId": "192",
    "_CreationDate": "2012-02-08T20:02:48.790",
    "_Score": "10",
    ...
    "_OwnerUserId": "6",
    ...
},
...

和用户:

{
    "_Id": "1",
    "_Reputation": "101",
    "_CreationDate": "2012-02-08T19:45:13.447",
    "_DisplayName": "Geoff Dalgas",
    ...
    "_AccountId": "2"
},
...

我想找到写 5 到 15 个帖子的用户。 这就是我的查询的样子:

db.posts.aggregate([
    {
        $lookup: {
            from: "users", 
            localField: "_OwnerUserId",
            foreignField: "_AccountId", 
            as: "X"
        }
    },  
    {
        $group: {
            _id: "$X._AccountId", 
            posts: { $sum: 1 }
        }
    },   
    {
        $match : {posts: {$gte: 5, $lte: 15}}
    },  
    {
        $sort: {posts: -1 }
    },
    {
        $project : {posts: 1}
    }
])

而且它的工作速度非常慢。对于 6k 个用户和 10k 个帖子,需要 40 多秒才能得到响应,而在关系数据库中,我会在一瞬间得到响应。 问题出在哪里?我刚刚开始使用 mongodb,很可能我搞砸了这个查询。

【问题讨论】:

  • 您尝试使用非关系数据库关联数据,然后发现它比关系数据库花费更多时间?您确实需要确保 _accountId 字段上有索引。
  • @bauman.space 这就是问题所在。现在只需要 700 毫秒!
  • 标记了答案,并附有指向其工作原理的链接。

标签: mongodb aggregation-framework


【解决方案1】:

只要您要按用户_AccountId 进行分组,您应该首先通过_OwnerUserId 执行$group,然后仅在过滤具有10<postsCount<15 的帐户后查找,这将减少查找:

db.posts.aggregate([{
    $group: {
      _id: "$_OwnerUserId",
      postsCount: {
        $sum: 1
      },
      posts: {
        $push: "$$ROOT"
      } //if you need to keep original posts data
    }
  },
  {
    $match: {
      postsCount: {
        $gte: 5,
        $lte: 15
      }
    }
  },
  {
    $lookup: {
      from: "users",
      localField: "_id",
      foreignField: "_AccountId",
      as: "X"
    }
  },
  {
    $unwind: "$X"
  },
  {
    $sort: {
      postsCount: -1
    }
  },
  {
    $project: {
      postsCount: 1,
      X: 1
    }
  }
])

【讨论】:

    【解决方案2】:

    首先使用$match,然后使用$lookup$match 将需要检查的行过滤到$lookup。这是有效的。

    【讨论】:

    • 以前的答案与你的答案完全相同!
    【解决方案3】:

    来自https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/

    foreignField 指定来自 from 中的文档的字段 收藏。 $lookup 在 foreignField 上执行相等匹配 输入文档中的 localField。如果一个文件在 from 集合不包含 foreignField,$lookup 将 值作为 null 用于匹配目的。

    这将与任何其他查询一样执行。

    如果您在字段 _AccountId 上没有索引,它将对 10,000 个帖子中的每一个执行全表扫描查询。大部分时间将花在该 tablescan 上。

    db.users.ensureIndex("_AccountId", 1) 
    

    加快了进程,因此它执行 10,000 次索引命中而不是 10,000 次表扫描。

    【讨论】:

    • 您有什么建议? “查找”与“在编辑时复制和更新所有记录”
    • 我需要这个。谢谢!
    【解决方案4】:

    除了 bauman.space 建议在 _accountId 字段上放置索引(这很关键)之外,您还应该进行 $match 在聚合管道中尽早阶段(即作为第一阶段)。即使它不使用任何索引(除非您为帖子字段编制索引),但它会在执行 $lookup (join) 阶段之前过滤结果集。

    您的查询非常慢的原因是对于每个帖子,它正在为每个用户进行非索引查找(顺序读取)。这大约是 60m 读取!

    查看MongoDB Aggregation Docs管道优化部分。

    【讨论】:

    • 是的,bauman.space 的提示真的很有帮助。关于查询中的 $match 位置,我可以在此特定查询中进行哪些更改?我可以看到如何改进我的查询,但我根本不熟悉 mongodb。
    • 只需将 $match 部分移动到要传递给聚合函数的数组中的第一个位置(即 $lookup 部分上方)。聚合就像管道一样工作,因此一个步骤的结果会传递到下一步,就像一个渐进式过滤过程。通过将 $match 部分放在 $lookup 部分之前,您可以将查找次数减少到仅通过 $match 步骤的那些记录。
    • 我很想知道每次更改对您的查询性能的改进程度。
    • 但在我的情况下 $match 使用 $group 中声明的posts 变量,因此更改阶段顺序将不起作用。如果我在 $group 之前移动 $match 我会得到空结果
    • 查看它的方式是您当前正在为每个不必要的帖子进行用户查找。您真的只想查找每个用户一次。所以我会按以下顺序进行:$group(按 _OwnerUserId)、$match、$lookup,然后是 $sort/$project。它应该会显着减少对用户表的读取次数。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-01-10
    • 2020-05-06
    • 1970-01-01
    • 1970-01-01
    • 2015-02-17
    • 2016-04-09
    • 1970-01-01
    相关资源
    最近更新 更多