随机排序顺序

And*_*Kon 11 mongodb mongodb-query

关于从收集中获取随机文档的方法的问题已被多次询问,并且有关于此主题的建议.

我需要的是从集合中获取几个随机文档,更糟糕的是 - 这些文档必须符合某些标准(过滤,我的意思).例如,我有一组文章,其中每篇文章都有一个"主题"字段.用户选择他感兴趣的主题,我的数据库必须每次以随机顺序显示相应的文章.

显然,之前讨论过的黑客行为对我没有帮助.实现我想要的唯一方法是仅查询相应的主题获取ID:

var arr = db.articles.find({topic: 3}, {_id:1}).toArray();
Run Code Online (Sandbox Code Playgroud)

然后根据接收的文档数量生成随机数字序列,然后使用随机数作为该数组的索引从数组中获取文档ID,然后最后再向mongodb请求获取具有随机选择的ID的文档.

正如你所看到的,它似乎有点太慢了,特别是,如果第一个查询返回的文章太多了:)

所以我认为可能有一些mongodb命令通过索引键根据它们在索引中的位置来获取文档.关键是我可以像这样创建覆盖复合索引:

db.articles.ensureIndex({topic: 1, _id:1});
Run Code Online (Sandbox Code Playgroud)

现在我的查询只需扫描索引中右_id的连续行.如果我可以通过那些'_ids'位置请求集合中的文档,那么我可以在一个请求中完成整个过程!就像是:

var cursor = db.articles.find({topic:3, $indexKeyPosition: {$in: myRandomSequence}});
Run Code Online (Sandbox Code Playgroud)

有谁知道这些功能?

Cla*_*diu 6

如今,您应该能够使用$sample聚合功能.

示例(未经测试):

db.articles.aggregate([
    { $match : { topic : 3 } },
    { $sample : { size: 3 } }
])
Run Code Online (Sandbox Code Playgroud)

但请注意,它可能会多次返回同一文档.

  • @StijnVanBael [docs](https://docs.mongodb.com/master/reference/operator/aggregation/sample/#pipe._S_sample)说“ **警告:$ sample可能在同一文档中多次输出同一文档结果集。**“ (2认同)

Sam*_*aye 5

所以我认为可能有一些 mongodb 命令可以根据文档在索引中的位置通过索引键获取文档。重点是我可以像这样创建覆盖复合索引:

不,MongoDB 中没有这样的函数,尽管能够随机化结果集是个好主意。与此同时,这里有一个 JIRA:https://jira.mongodb.org/browse/SERVER-533

由于无法从索引位置进行选择,以便它可以使用索引并因此使用单次往返,因此您别无选择,只能打开多个游标。

当前的解决方案取决于结果集中有多少文档。

如果结果集中的文档数量较少,您可以通过简单的方法解决此问题,skip(rand())但是limit(1)您必须意识到两者skip()limit()没有有效地使用索引。

这并不意味着它将扫描整个 Btree,而是意味着它将扫描到您为止skip()

这意味着,如果您的结果集变大并且rand()变得很大,您将看到严重的性能问题,就像许多人一样。

可能解决此问题的一个好方法是维护:

并使用该新字段“跳过”使用查询的其余部分,例如:

var arr = db.articles.find({topic: 3, rand: rand()}, {_id:1}).limit(7).toArray();
Run Code Online (Sandbox Code Playgroud)

0使用to idea将得到 7 个随机行1

其随机排序能力依赖于不断变化的数据集来帮助在排序中创建随机性。当然,如果结果集持续静态,这将不起作用。

至于使用batchSize,它在这里变得无关紧要,事实上通常如此。例如,使用 BatchSize 获取所有结果的逻辑并不完全有意义,因为 BatchSize 通常具有 16MB 的绝对最大大小。这意味着,如果您的文档很大,您可能无法获得您认为的单次往返。

这也仅表明服务器将立即发送所有这些数据,它并不表示服务器上的工作量,而仅表示一次通过线路发送的数据量。

因此,考虑到您必须使用多个游标来执行此操作(我推荐的方式),您可以运行:

var arr = db.articles.find({topic: 3, rand: {$gte:rand()}}).sort({rand:1}).limit(1);
Run Code Online (Sandbox Code Playgroud)

多次,或者无论您需要多少次,都可以。这与游标的正常迭代没有太大区别,只要您有正确的索引,速度应该会相当快。

还有另一种方法,但我不推荐它。您可以运行一次 MR,例如每小时一次或创建另一个集合的操作_idrand()这意味着您可以执行我展示的第一个查询:

var arr = db.articles.find({topic: 3, rand: rand()}, {_id:1}).limit(7).toArray();
Run Code Online (Sandbox Code Playgroud)

确实得到 7 个随机记录,因为rand()当然会有所不同。但这不是实时的,对于大型数据集上的服务器来说也不是很好,因此我不推荐这样的事情。

编辑

还有另一种方法。使用自动递增 id,您可以执行一个语句来一次$or选出 7个。rand()然而这又引入了另一个问题,即删除。

如果删除任何行,您可能会遇到rand()不存在的行,因此不会返回任何行。由于不会维护自动递增 id 来计数器删除服务器端,因此您必须自己执行此操作。这不是一件容易或可扩展的事情。

要添加到此$or语句不能是limit()ed on 子句,这意味着您无法通过执行 sub select type$or使 MongoDB 仅$or使用 为每个子句挑选一个结果来解决此问题$gte

这同样适用于和rand()之间。这适用于if you can limit 子句。01$or


fra*_*val 2

您可以(如分页)计算有多少文档与查询匹配。然后使用skip(random_value)和limit(1)进行N次查询。

db.collection.count({field:value,field2:value2})

db.collection.find({field:value,field2:value2}).skip(n).limit(1)
Run Code Online (Sandbox Code Playgroud)

如果集合针对查询建立了索引,那么它必须很快。

  • 不,skip和limit遍历整棵树。之前已经讨论过很多次了,这种方法效率低下......除此之外,你建议提出几个听起来不太快的请求...... (3认同)