我想从一个巨大的集合(1亿条记录)中获得一个随机记录。

最快最有效的方法是什么?

数据已经在那里,没有字段可以生成随机数并获得随机行。


当前回答

当我面对类似的解决方案时,我回溯并发现业务请求实际上是为了创建所呈现的库存的某种形式的轮换。在这种情况下,有更好的选择,它们有来自Solr这样的搜索引擎的答案,而不是MongoDB这样的数据存储。

In short, with the requirement to "intelligently rotate" content, what we should do instead of a random number across all of the documents is to include a personal q score modifier. To implement this yourself, assuming a small population of users, you can store a document per user that has the productId, impression count, click-through count, last seen date, and whatever other factors the business finds as being meaningful to compute a q score modifier. When retrieving the set to display, typically you request more documents from the data store than requested by the end user, then apply the q score modifier, take the number of records requested by the end user, then randomize the page of results, a tiny set, so simply sort the documents in the application layer (in memory).

如果用户的范围太大,可以将用户划分为行为组,按行为组而不是按用户进行索引。

如果产品范围足够小,您可以为每个用户创建一个索引。

我发现这种技术效率更高,但更重要的是在创建相关的、有价值的软件解决方案使用体验方面更有效。

其他回答

您可以选择一个随机时间戳,然后搜索随后创建的第一个对象。 它将只扫描单个文档,尽管它不一定会给您一个统一的分布。

var randRec = function() {
    // replace with your collection
    var coll = db.collection
    // get unixtime of first and last record
    var min = coll.find().sort({_id: 1}).limit(1)[0]._id.getTimestamp() - 0;
    var max = coll.find().sort({_id: -1}).limit(1)[0]._id.getTimestamp() - 0;

    // allow to pass additional query params
    return function(query) {
        if (typeof query === 'undefined') query = {}
        var randTime = Math.round(Math.random() * (max - min)) + min;
        var hexSeconds = Math.floor(randTime / 1000).toString(16);
        var id = ObjectId(hexSeconds + "0000000000000000");
        query._id = {$gte: id}
        return coll.find(query).limit(1)
    };
}();

对所有记录进行计数,生成一个0到计数之间的随机数,然后执行:

db.yourCollection.find().limit(-1).skip(yourRandomNumber).next()

从MongoDB 3.2版本开始,你可以使用$sample聚合管道操作符从集合中随机获得N个文档:

// Get one random document from the mycoll collection.
db.mycoll.aggregate([{ $sample: { size: 1 } }])

如果你想从集合的筛选子集中选择随机文档,在管道中预先添加$match阶段:

// Get one random document matching {a: 10} from the mycoll collection.
db.mycoll.aggregate([
    { $match: { a: 10 } },
    { $sample: { size: 1 } }
])

正如注释中所指出的,当size大于1时,返回的文档样例中可能有重复项。

没有一个解决方案对我有效。尤其是当缝隙多、集小的时候。 这对我来说很好(在php中):

$count = $collection->count($search);
$skip = mt_rand(0, $count - 1);
$result = $collection->find($search)->skip($skip)->limit(1)->getNext();

如果没有数据,这是很困难的。_id字段是什么?它们是mongodb对象id吗?如果是这样,你可以得到最大值和最小值:

lowest = db.coll.find().sort({_id:1}).limit(1).next()._id;
highest = db.coll.find().sort({_id:-1}).limit(1).next()._id;

然后,如果你假设id是均匀分布的(但它们不是,但至少这是一个开始):

unsigned long long L = first_8_bytes_of(lowest)
unsigned long long H = first_8_bytes_of(highest)

V = (H - L) * random_from_0_to_1();
N = L + V;
oid = N concat random_4_bytes();

randomobj = db.coll.find({_id:{$gte:oid}}).limit(1);