15

关于如何从集合中获取随机文档的问题已经被问过很多次,并且有关于这个主题的建议。

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

显然,之前讨论的 hack 对我没有帮助。实现我想要的唯一方法是查询仅获取 id 的相应主题:

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

然后根据接收到的文档数量生成随机数字序列,然后使用随机数作为该数组的索引从数组中获取文档 id,最后向 mongodb 发出另一个请求以获取具有这些随机选择的 id 的文档。

如您所见,这似乎有点太慢了,特别是如果第一次查询返回的文章太多:)

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

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

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

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

有谁知道这些功能?

4

3 回答 3

7

现在,你应该可以使用$sample聚合功能了。

示例(未经测试):

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

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

于 2016-04-20T15:46:32.550 回答
4

所以我认为可能有一些 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();

0将使用to 的1想法得到 7 个随机行。

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

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

这也只规定服务器将一次发送所有这些数据,它并不表示放置在服务器上的工作量,只是一次通过网络发送的数据量。

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

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

几次,或者无论你需要多少次,都会重复。这与游标的正常迭代没有太大区别,只要您拥有正确的索引,应该会非常快。

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

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

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

编辑

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

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

要添加到此$or语句中不能limit()ed on 子句,这意味着您无法通过执行 sub select type$or来解决此问题,以使 MongoDB 仅$or使用$gte.

这同样适用于rand()between01$or如果你可以限制子句,这将适用。

于 2012-12-17T08:53:32.983 回答
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)

如果集合为查询建立索引,它必须很快。

于 2012-12-17T08:46:11.730 回答