mongodb的随机查询不像mysql一样可以利用rand()函数,取随机数查询数据,mongodb在查询中,并没有类似rand()的查询方法 (mongodb当前版本1.6.3)
题外话: mysql的rand在执行效率上有一定问题 sql 需要优化 详见
下面是一个具体的例子和解决方案
部分参照官网的mongodb cookbook 文章 The Random Attribute 译过来的,官网废话太多了
假如你现在执行如下mongodb查询
1 | photos. find ({ "author" : "John Doe" }) |
查询结果是获取author为John Doe的所有数据,假如你只是想获取随机的几条数据,你可以查询后,计算数据总条数,然后rand结果数组的几条,就可以实现了
但对于几十万数据或高频度查询来说,这种查询几乎是致命的.
所以在你向数据库里插入数据的时候,可以预先定义一个RA字段(随机属性字段),插入RA字段一个随机数,用随机数查询
2 | > db.docs.save( { key : 1, ..., random : Math.random() } ) |
3 | > db.docs.save( { key : 1, ..., random : Math.random() } ) |
4 | > db.docs.save( { key : 2, ..., random : Math.random() } ) |
5 | ... many more insertions with 'key : 2' ... |
6 | > db.docs.save( { key : 2, ..., random : Math.random() } ) |
Math.random()实现了js中的随机数方法, 如果你使用的是Math.random() ,会获取一个0~1之间的随机数
08 | { "_id" : ObjectId( "4bfa81198cf5fc1002a42b91" ), "key" : 2, "random" : 0.23578915913357468} |
10 | { "_id" : ObjectId( "4bfa81198cf5fc1002a42b93" ), "key" : 2, "random" : 0.8983254666113549 } |
如果你使用了上面的解决方案,那么你还要给随机的字段的数据库索引,比如上面的random
1.随机查询单条记录
如果想随机查询单条记录,那么只需要变更一下请求,对RA字段添加适当的过滤条件. 下面尝试着查询跟RA字段中的值最接近的记录
2 | > result = db.docs.findOne( { key : 2, random : { $gte : rand } } ) |
3 | > if ( result == null ) { |
4 | > result = db.docs.findOne( { key : 2, random : { $lte : rand } } ) |
1.取rand 随机数
2.查询random字段中大于rand的单条记录
3.判断有无数据,无数据则查询小于rand的单条记录
使用这种方法的时候,很重要的一点是建立数据库索引
1 | > db.docs.ensureIndex( { key : 1, random :1 } ) |
2. 对样本数据进行map / reduce 查询
如果数据和计算量过大,可以取样本数据,然后对结果的RA字段进行如上过滤处理
02 | > for (i=0; i < 10000; i++) { db.docs.save( { key : i % 10, rand : Math.random() } ) } |
03 | > m = function () { emit(this.key, 1); } |
04 | > r = function (k, vals) { |
06 | for (var i in vals) sum += vals[i]; |
10 | > res = db.docs.mapReduce(m, r, { query : { key : 2, rand : { $lte: sample } } }) |
mongodb 将会执行query查询,只有符合条件的10%数据才会执行mapper方法,相较于全文查询,运行时间会大大减少
我本地测试查询key为2 ,且RA字段小于0.1的数据计数为98 很接近100 官网的数据为85,本地结果如下
02 | "result" : "tmp.mr.mapreduce_1288863869_7" , |
默认情况下map/reduce生成的collection是临时的,只要你断掉本次连接就会自动删除,所以查询结果,仅当次连接有效
.
01 | > res = db.docs.mapReduce(m, r, { query : { rand : { $lte : 0.1 } } }) |
05 | > db[res.result]. find () |
06 | { "_id" : 0, "value" : 98 } |
07 | { "_id" : 1, "value" : 103 } |
08 | { "_id" : 2, "value" : 89 } |
09 | { "_id" : 3, "value" : 99 } |
10 | { "_id" : 4, "value" : 100 } |
11 | { "_id" : 5, "value" : 115 } |
12 | { "_id" : 6, "value" : 103 } |
13 | { "_id" : 7, "value" : 92 } |
14 | { "_id" : 8, "value" : 111 } |
15 | { "_id" : 9, "value" : 108 } |
在map/reduce 查询随机数据的实例中,可以不必像第一种方法为属性创建库索引
附加说明
使用Math.random()插入数据进行查询是,概率的数会偏大,所以不一定能保证查询的结果是按照概率均匀分布的
1 | > db.docs.save( { key : 1, random : Math.random() } ) |
2 | > db.docs.save( { key : 1, random : Math.random() } ) |
4 | { "_id" : ObjectId( "4bfa9585cffdb770c08e7cc9" ), "key" : 1, "random" : 0.9988383572723725 } |
5 | { "_id" : ObjectId( "4bfa9586cffdb770c08e7cca" ), "key" : 1, "random" : 0.8 |