我希望得到一张巨大的(1亿张唱片)的随机唱片mongodb
.
什么是最快,最有效的方法?数据已经存在,并且没有可以生成随机数并获得随机行的字段.
有什么建议?
从3.2版本的MongoDB开始,您可以使用$sample
聚合管道运算符从集合中获取N个随机文档:
// Get one random document from the mycoll collection.
db.mycoll.aggregate([{ $sample: { size: 1 } }])
计算所有记录,生成0到计数之间的随机数,然后执行:
db.yourCollection.find().limit(-1).skip(yourRandomNumber).next()
3.2将$ sample引入聚合管道.
关于将其付诸实践的博客文章也很好.
这实际上是一个功能请求:http: //jira.mongodb.org/browse/SERVER-533但它是在"无法修复"下提交的.
这本食谱有一个很好的方法可以从一个集合中选择一个随机文档:http: //cookbook.mongodb.org/patterns/random-attribute/
要解释配方,您可以为文档分配随机数:
db.docs.save( { key : 1, ..., random : Math.random() } )
然后选择一个随机文档:
rand = Math.random() result = db.docs.findOne( { key : 2, random : { $gte : rand } } ) if ( result == null ) { result = db.docs.findOne( { key : 2, random : { $lte : rand } } ) }
查询两者$gte
并且$lte
必须找到最接近随机数的文档rand
.
当然,您需要在随机字段上编制索引:
db.docs.ensureIndex( { key : 1, random :1 } )
如果您已经查询了索引,只需将其删除,附加random: 1
到它,然后重新添加即可.
您还可以使用MongoDB的地理空间索引功能来选择最接近随机数的文档.
首先,在集合上启用地理空间索引:
db.docs.ensureIndex( { random_point: '2d' } )
要在X轴上创建一组带有随机点的文档:
for ( i = 0; i < 10; ++i ) { db.docs.insert( { key: i, random_point: [Math.random(), 0] } ); }
然后你可以从集合中获得一个随机文档,如下所示:
db.docs.findOne( { random_point : { $near : [Math.random(), 0] } } )
或者您可以检索最接近随机点的多个文档:
db.docs.find( { random_point : { $near : [Math.random(), 0] } } ).limit( 4 )
这只需要一个查询而不需要空检查,而且代码干净,简单且灵活.您甚至可以使用geopoint的Y轴为查询添加第二个随机性维度.
以下配方比mongo cookbook解决方案稍慢(在每个文档上添加随机键),但返回更均匀分布的随机文档.它比skip( random )
解决方案的分布更不均匀,但在删除文档的情况下更快,更安全.
function draw(collection, query) {
// query: mongodb query object (optional)
var query = query || { };
query['random'] = { $lte: Math.random() };
var cur = collection.find(query).sort({ rand: -1 });
if (! cur.hasNext()) {
delete query.random;
cur = collection.find(query).sort({ rand: -1 });
}
var doc = cur.next();
doc.random = Math.random();
collection.update({ _id: doc._id }, doc);
return doc;
}
它还要求您在文档中添加一个随机的"随机"字段,因此在创建它们时不要忘记添加它:您可能需要初始化您的集合,如Geoffrey所示
function addRandom(collection) {
collection.find().forEach(function (obj) {
obj.random = Math.random();
collection.save(obj);
});
}
db.eval(addRandom, db.things);
基准测试结果
这种方法比skip()
(ceejayoz)方法快得多,并且生成比迈克尔报告的"cookbook"方法更均匀的随机文档:
对于包含1,000,000个元素的集合:
这种方法在我的机器上花费不到一毫秒
该skip()
方法平均需要180毫秒
食谱方法将导致大量文档永远不会被选中,因为它们的随机数不支持它们.
此方法将随时间均匀地选取所有元素.
在我的基准测试中,它比食谱方法慢了30%.
随机性并非100%完美,但它非常好(必要时可以改进)
这个配方并不完美 - 完美的解决方案将是其他人注意到的内置功能.
然而,它应该是许多目的的妥协.
这是一种使用默认ObjectId
值_id
和一些数学和逻辑的方法.
// Get the "min" and "max" timestamp values from the _id in the collection and the
// diff between.
// 4-bytes from a hex string is 8 characters
var min = parseInt(db.collection.find()
.sort({ "_id": 1 }).limit(1).toArray()[0]._id.str.substr(0,8),16)*1000,
max = parseInt(db.collection.find()
.sort({ "_id": -1 })limit(1).toArray()[0]._id.str.substr(0,8),16)*1000,
diff = max - min;
// Get a random value from diff and divide/multiply be 1000 for The "_id" precision:
var random = Math.floor(Math.floor(Math.random(diff)*diff)/1000)*1000;
// Use "random" in the range and pad the hex string to a valid ObjectId
var _id = new ObjectId(((min + random)/1000).toString(16) + "0000000000000000")
// Then query for the single document:
var randomDoc = db.collection.find({ "_id": { "$gte": _id } })
.sort({ "_id": 1 }).limit(1).toArray()[0];
这是shell表示的一般逻辑,易于适应.
所以要点:
查找集合中的最小和最大主键值
生成一个介于这些文档的时间戳之间的随机数.
将随机数添加到最小值,并查找大于或等于该值的第一个文档.
这使用"十六进制"中时间戳值的"填充"来形成有效值,ObjectId
因为这是我们正在寻找的.使用整数作为_id
值本质上是简单的,但在点上是相同的基本思想.
在Python中使用pymongo:
import random def get_random_doc(): count = collection.count() return collection.find()[random.randrange(count)]
如果那里没有关键数据,那就太难了._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);
现在您可以使用聚合.例:
db.users.aggregate( [ { $sample: { size: 3 } } ] )
见文档.
您可以选择随机时间戳并搜索之后创建的第一个对象.它只扫描单个文档,但不一定能为您提供统一的分发.
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)
};
}();
使用Python(pymongo),聚合函数也可以使用。
collection.aggregate([{'$sample': {'size': sample_size }}])
这种方法比运行查询随机数(例如collection.find([random_int])的速度快得多。对于大型集合,尤其如此。