【问题标题】:How to maintain a sorted list in database?如何维护数据库中的排序列表?
【发布时间】:2022-01-25 08:41:26
【问题描述】:

我有一个非常大的用户列表(数百万)存储在数据库中(在我们的例子中是 Mongo)。每个用户都有一个与之关联的分数。分数会根据用户的操作不断变化。

当列表按分数排序时,我希望能够查询每个用户的位置。

现在,我有一个分数索引。我查询所有用户,按分数对其进行排序,然后遍历以找到我想要的用户的位置。

var users = User.find({})
  .sort({
    score: -1
  });

var position = 1;
for (
  let user = await users.next(); user !== null; user = await users.next()
) {
  if (user === targetUser) {
    return position;
  }
  position++;
}

排序是一项缓慢的操作,但据我了解,索引已经对其进行排序,因此这仅与获取所有记录一样昂贵。

由于列表已排序,我也许可以通过执行二进制搜索来提高查找元素的速度。

更大的问题是将列表存储在内存中(我一直在 Node 中遇到堆内存不足的错误)。

什么是正确的数据库+数据结构来实现我想要的?

【问题讨论】:

  • 如何给score添加索引,然后对score进行范围查询,按score排序,然后进行count。这应该通过分数给出用户的相对位置。请记住,具有相同分数的用户的位置对彼此来说有些随意。您尚未指定如何对具有相同分数的人进行排名。
  • 能否详细说明范围查询?
  • 范围查询是使用 $lt 或 $gt 运算符(小于或大于)的查询。
  • 知道了。我用您建议的解决方案添加了答案。我认为它有助于解决内存问题,但仍然很慢。

标签: database mongodb sorting mongodb-query


【解决方案1】:

您可以利用从 MongoDB v5.0+ 开始提供的 $setWindowFields 和 $rank。

db.collection.aggregate([
  {
    "$setWindowFields": {
      "partitionBy": null,
      "sortBy": {
        "score": -1
      },
      "output": {
        "rank": {
          $rank: {}
        }
      }
    }
  },
  {
    $match: {
      _id: <target user's identifier>
    }
  }
])

这是Mongo playground 供您参考。

【讨论】:

  • 哦,这真的很有趣!谢谢你。升级到 Mongo 5 试试看。问题:如果我想按分数和注册日期等多个字段进行排序怎么办? $rank 似乎只适用于单个字段。
  • @ask 只需更新 sortBy 子句就可以了
  • 我试过了,但得到了这个错误MongoError: $rank must be specified with a top level sortBy expression with exactly one element,这表明它只有在你对单个字段进行排序时才有效。
  • @ask 你是对的。我刚刚在操场上尝试过,并确认$sortBy 中只允许使用 1 个密钥。 This 是一个稍微修改的版本,我为$rank 构造了一个辅助sortKey 字段。您可以通过$subtract 或其他映射来操作单个排序来控制sortKey。希望这会有所帮助。
【解决方案2】:

@barrypicker 我试过了,它稍微快一点,但仍然很慢(大约 20 秒)。但我认为它可能会解决我的内存问题?

  // Get target user
  let target = await User.findOne({
    _id: targetId
  });

  // Find number of users with lower score without bringing all of them into memory
  let lowerScoreCount = await User.count({
    score: { $lt: target.score }
  });

  // Find all users with same score
  let sameScoreSignups = await User.findOne({
    score: target.score
  });

  // Iterate over users with same score till you find your user and add this to the lower score count to get the position of the user in the sorted list
  let position = lowerScoreCount;
  sameScoreSignups.forEach((user) => {
    if (user._id === targetId) {
      return position;
    }
    position++;
  });

  return position;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-01-18
    • 1970-01-01
    • 2021-09-21
    • 1970-01-01
    • 2016-05-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多