關於從mongodb庫中隨機取出一條記錄的方法的博文很多,其中都提到了下面三種方法:
1、skip過隨機數量的記錄。
DBCursor cursor = coll.find(query);
int rint = random.nextInt(cursor.count());
cursor.skip(rint);
DBObject word = null;
if(cursor.hasNext()){
word = cursor.next();
cursor.close();
}
很多人說不推薦這種做法,如果數據量不是那么巨大,10萬個記錄以內完全可以使用這種方法。
其實讓我寫存儲引擎的話,我肯定把這個方法搞成最快的。只查詢,又不排序。用作查詢的條件建好索引后,至少和方法2一樣快。通過索引進行物理skip很容易直接就定位到要找的數據了。
2、增加一個random數值字段。
var random=Math.random();
var result=db.user.findOne({"random":{"$lt":random}});
if(result==null){
result=db.user.findOne({"random":{"$gte":random}});
}
很多人說這是效率較高的一種可行方法,但是如果你信了,又不加驗證,那真叫坑了爹了!下面我們就分析一下。
findOne的機制是取出查詢結果的第一條。這有什么問題看出來了嗎?
內部的查詢結果是按random字段排序的嗎?問題顯然不是那么好回答吧!通過試驗發現:如果不建立索引,查詢的結果是按照存儲順序排的。也就是說不管查詢條件是小於,還是大於等於,都會取到符合條件的最早入庫的記錄。這大大降低了隨機性,有沒有啊。如果建立索引,查詢的結果是按照random由小到大排序的,此時如果用小於等於條件,始終取回來的是random值最小的值。這就沒有隨機性了,是不是呢。也就是說,如果random升序需要使用大於條件,如果降序需要使用小於條件。但是沒有指定排序的時候內部怎么給排序,你敢肯定嗎?如果不敢肯定,是不是就得指定排序方式。從底層來說,你指定的排序方式跟索引的物理存放順序一致才能達到最高的效率。
3、增加一個random空間位置字段。
db.coll.ensureIndex({ random: '2d' })
result = db.coll.findOne({ random: { $near: [Math.random(), 0] } })
將random建立為多值字段,兩值得就可以,建立索引的時候當地理位置使用。取值的時候隨機生成一個坐標,然后取離這個點最近一個值。
很多人把這個方法也列在推薦方法中,但是真的是高效率的方法嗎?這個方法比較麻煩,很多人應該都沒驗證,直接抄寫到自己博客了。其實用腦袋想想,這個方法比第二個方法更不靠譜呢。可以自己思考一種算法——在一群坐標中找出離某個坐標最近的一個坐標。好難哦,坐標點哎,他們本身並沒有一個大小關系,也就是說沒有天然的順序。那些牛逼的查找算法都是基於排序的(基於哈希的本質也是基於排序的)。
那么怎么辦?方法一:對兩個分量建立兩個索引。然后以查詢點為中心點構建一個較小的矩形區域,查詢的時候就是兩個分量的范圍的與關系。如果沒查到結果擴大矩形區域,如果查詢的結果過多,縮小矩形區域,這里就可以用二分法了。然后在矩形區域內精確計算距離。方法二:空間點按照相互距離聚類,然后將類別中心存儲起來,然后采用方法一找到一個最近的類別中心,然后再在這個內別中使用方法一。方法三:求出所有點的外接矩形,然后把區域等分成4塊的方法構建4叉樹索引。構建過程就是把某個區域分成四等分,再把其中一份分成4等分,直到每個區域的點數少於給定值。這樣查詢的時候每層確定一次范圍。最后一層暴力計算距離。我是想不出來更牛B的方法了!
可見最近空間距離的計算不輕松啊!
