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

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

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


当前回答

为了获得确定数量的无重复的随机文档:

first get all ids get size of documents loop geting random index and skip duplicated number_of_docs=7 db.collection('preguntas').find({},{_id:1}).toArray(function(err, arr) { count=arr.length idsram=[] rans=[] while(number_of_docs!=0){ var R = Math.floor(Math.random() * count); if (rans.indexOf(R) > -1) { continue } else { ans.push(R) idsram.push(arr[R]._id) number_of_docs-- } } db.collection('preguntas').find({}).toArray(function(err1, doc1) { if (err1) { console.log(err1); return; } res.send(doc1) }); });

其他回答

使用Map/Reduce,您当然可以获得一个随机记录,只是不一定非常有效,这取决于您最终使用的过滤集合的大小。

我已经用5万个文档测试了这个方法(过滤器将其减少到大约3万个),它在Intel i3、16GB ram和SATA3 HDD上执行大约400毫秒……

db.toc_content.mapReduce(
    /* map function */
    function() { emit( 1, this._id ); },

    /* reduce function */
    function(k,v) {
        var r = Math.floor((Math.random()*v.length));
        return v[r];
    },

    /* options */
    {
        out: { inline: 1 },
        /* Filter the collection to "A"ctive documents */
        query: { status: "A" }
    }
);

Map函数简单地创建一个数组,其中包含所有与查询匹配的文档的id。在我的例子中,我测试了5万个可能的文档中的大约3万个。

Reduce函数只是在数组中从0到项数(-1)之间选择一个随机整数,然后从数组中返回该_id。

400ms听起来是一段很长的时间,而且确实如此,如果您有5000万条记录而不是5万条记录,这可能会增加开销,以至于在多用户情况下无法使用。

MongoDB在核心中包含这个功能有一个悬而未决的问题…https://jira.mongodb.org/browse/SERVER-533

如果将这种“随机”选择构建到索引查找中,而不是将id收集到一个数组中然后选择一个,这将非常有帮助。(去投票吧!)

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

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)
    };
}();

有效可靠的方法是:

在每个文档中添加一个名为“random”的字段,并为其分配一个随机值,为该随机字段添加一个索引,如下所示:

让我们假设我们有一个名为“links”的网络链接集合,我们想从它中随机链接:

link = db.links.find().sort({random: 1}).limit(1)[0]

为了确保同一个链接不会第二次弹出,用一个新的随机数更新它的随机场:

db.links.update({random: Math.random()}, link)

我对php的解决方案:

/**
 * Get random docs from Mongo
 * @param $collection
 * @param $where
 * @param $fields
 * @param $limit
 * @author happy-code
 * @url happy-code.com
 */
private function _mongodb_get_random (MongoCollection $collection, $where = array(), $fields = array(), $limit = false) {

    // Total docs
    $count = $collection->find($where, $fields)->count();

    if (!$limit) {
        // Get all docs
        $limit = $count;
    }

    $data = array();
    for( $i = 0; $i < $limit; $i++ ) {

        // Skip documents
        $skip = rand(0, ($count-1) );
        if ($skip !== 0) {
            $doc = $collection->find($where, $fields)->skip($skip)->limit(1)->getNext();
        } else {
            $doc = $collection->find($where, $fields)->limit(1)->getNext();
        }

        if (is_array($doc)) {
            // Catch document
            $data[ $doc['_id']->{'$id'} ] = $doc;
            // Ignore current document when making the next iteration
            $where['_id']['$nin'][] = $doc['_id'];
        }

        // Every iteration catch document and decrease in the total number of document
        $count--;

    }

    return $data;
}

如果没有数据,这是很困难的。_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);