随机排序顺序

时间:2012-12-17 08:38:49

标签: mongodb mongodb-query

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

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

显然,之前讨论过的黑客攻击对我没用。实现我想要的唯一方法是查询相应的主题获取ID:

var arr = db.articles.find({topic: 3}, {_id:1}).toArray();

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

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

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

db.articles.ensureIndex({topic: 1, _id:1});

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

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

有没有人知道这些功能?

3 个答案:

答案 0 :(得分:6)

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

示例(未经测试):

db.articles.aggregate([
    { $match : { topic : 3 } },
    { $sample : { size: 3 } }
])

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

答案 1 :(得分:4)

  

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

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

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

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

如果您的结果集中包含少量文档,则可以使用简单skip(rand())limit(1)解决此问题,但您必须了解skip()和{{1}无法有效使用索引。

这并不意味着它会扫描整个Btree,这意味着它会扫描到你limit()

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

可能解决这个问题的一个好方法是维持:

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

1

使用var arr = db.articles.find({topic: 3, rand: rand()}, {_id:1}).limit(7).toArray(); 0的想法会获得7个随机行。

此随机排序功能依赖于不断变化的数据集,以帮助在排序中创建随机性。当然,如果结果集是连续静态的,那么这将不起作用。

至于使用batchSize,它在这里变得无关紧要,通常情况下也是如此。例如,使用BatchSize获取所有结果的逻辑并不完全合理,因为BatchSize的绝对最大大小通常为16MB。这意味着如果您的文件很大,您可能无法获得您认为自己的单程往返。

这也只是要求服务器一次发送所有这些数据,它不表示服务器上的工作量,只是表示一次通过线路发送的数据量。

因此,考虑到你必须使用mutliple游标(我建议的方式),你可以运行:

1

有几个,或者你需要多少,时间结束。这与游标的正常迭代没有太大的不同,只要你有正确的索引应该非常快。

还有另一种方法,但我不推荐它。你可以运行一个MR,比如每小时运行一次,或者创建另一个var arr = db.articles.find({topic: 3, rand: {$gte:rand()}}).sort({rand:1}).limit(1); _id的集合,这意味着你可以进行我展示的第一个查询:

rand()

真正获得7个随机记录,因为var arr = db.articles.find({topic: 3, rand: rand()}, {_id:1}).limit(7).toArray(); 当然会有所不同。但这不是实时的,对于大型数据集上的服务器也不是很好,我不建议这样做。

修改

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

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

添加到此rand()语句不能$or ed on子句,这意味着你不能通过子选择类型limit()来解决这个问题,使MongoDB只选择一个每个$or子句的结果使用$or

同样适用于$gterand()之间的0。如果您可以限制条款,这将适用于1

答案 2 :(得分: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)

如果集合是为查询编制索引的,那么它必须很快。