編程學習網 > 數據庫 > MongoDB > MongoDB 分頁查詢的方法及性能
2014
12-11

MongoDB 分頁查詢的方法及性能

  最近有點忙,本來有好多東西可以總結,Redis系列其實還應該有四、五、六…不過《Redis in Action》還沒讀完,等讀完再來總結,不然太水,對不起讀者。

  自從上次Redis之后呢,算是對Nosql類型的產品有些入門了,這會換個方向,研究下真正的NoSql數據庫——MongoDB。說起MongoDB,確實是用完了之后顛覆了我的數據管和程序觀。怎么說呢?如果用在OO設計的程序里那真的太棒了,像我這種數據驅動、表驅動思想根深蒂固的人,思路很難一下子跟上MongoDB的節奏。當然并不是調用個api,寫幾句query那些思路,而是程序設計思路,業務領域的設計,如果OO,如何適合展現,適合查詢,適合聚合運算等等。總之MongoDB重要的是程序的設計,設計好了,其實完全就忽略了Mongo的存儲,因為mongodb實在是太方便了。

  廢話不多說,關于入門的資料、安裝以及其他請拉到文章末尾,我附上了一些資料,以后如有必要再來分享。這篇文章著重的講講MongoDB的分頁查詢,為啥?分頁可是常見的頭號殺手,弄不好了,客戶罵,經理罵。

 傳統的SQL分頁

  傳統的sql分頁,所有的方案幾乎是繞不開row_number的,對于需要各種排序,復雜查詢的場景,row_number就是殺手锏。另外,針對現在的web很流行的poll/push加載分頁的方式,一般會利用時間戳來實現分頁。 這兩種分頁可以說前者是通用的,連Linq生成的分頁都是row_number,可想而知它多通用。后者是無論是性能和復雜程度都是最好的,因為只要簡單的一個時間戳即可。

 MongoDB分頁

  進入到Mongo的思路,分頁其實并不難,那難得是什么?其實倒也沒啥,看明白了也就那樣,和SQL分頁的思路是一致的。

  先說明下這篇文章使用的用例,我在數據庫里導入了如下的實體數據,其中cus_id、amount我生成為有序的數字,倒入的記錄數是200w:

public class Test
{
        /// <summary>
        /// 主鍵 ObjectId 是MongoDB自帶的主鍵類型
        /// </summary>
        public ObjectId Id { get; set; }
        /// <summary>
        /// 客戶編號
        /// </summary>
        [BsonElement("cust_id")]
        public string CustomerId { get; set; }
        /// <summary>
        /// 總數
        /// </summary>
        [BsonElement("amount")]
        public int Amount { get; set; }
        /// <summary>
        /// 狀態
        /// </summary>
        [BsonElement("status")]
        public string Status { get; set; }
}

  以下的操作基于MongoDB GUI 工具見參考資料3

  首先來看看分頁需要的參數以及結果,一般的分頁需要的參數是:

  • PageIndex    當前頁
  • PageSize      每頁記錄數
  • QueryParam[]  其他的查詢字段

  所以按照row_number的分頁思想,也就是說取第(pageIndex*pageSize)到第(pageIndex*pageSize + pageSize),我們用Linq表達就是:

query.Where(xxx...xxx).Skip(pageIndex*pageSize).Take(pageSize)

  查找了資料,還真有skip函數,而且還有Limit函數 見參考資料1、2,于是輕易地實現了這樣的分頁查詢:

db.test.find({xxx...xxx}).sort({"amount":1}).skip(10).limit(10)//這里忽略掉查詢語句

  相當的高效,幾乎是幾毫秒就出來了結果,果然是NoSql效率一流。但是慢,我這里使用的數據只是10條而已,并沒有很多數據。我把數據加到100000,效率大概是20ms。如果這么簡單就研究結束了的話,那真的是太辜負了程序猿要鉆研的精神了。sql分頁的方案,方案可是能有一大把,效率也是不一的,那Mongo難道就這一種,答案顯然不是這樣的。另外是否效率上,性能上會有問題呢?Redis篇里,就吃過這樣的虧,亂用Keys。

  在查看了一些資料之后,發現所有的資料都是這樣說的:

  不要輕易使用Skip來做查詢,否則數據量大了就會導致性能急劇下降,這是因為Skip是一條一條的數過來的,多了自然就慢了。

  這么說Skip就要避免使用了,那么如何避免呢?首先來回顧SQL分頁的后一種時間戳分頁方案,這種利用字段的有序性質,利用查詢來取數據的方式,可以直接避免掉了大量的數數。也就是說,如果能附帶上這樣的條件那查詢效率就會提高,事實上是這樣的么?我們來驗證一下:

  這里我們假設查詢第100001條數據,這條數據的Amount值是:2399927,我們來寫兩條語句分別如下:

db.test.sort({"amount":1}).skip(100000).limit(10)  //183ms


db.test.find({amount:{$gt:2399927}}).sort({"amount":1}).limit(10)  //53ms

  結果已經附帶到注釋了,很明顯后者的性能是前者的三分之一,差距是非常大的。也印證了Skip效率差的理論。

 C#實現

  上面已經談過了MongoDB分頁的語句和效率,那么我們來實現C#驅動版本。

  本篇文章里使用的是官方的BSON驅動,詳見參考資料4。Mongo驅動附帶了另種方式一種是類似ADO.NET的原生query,一種是Linq,這里我們兩種都實現

掃碼二維碼 獲取免費視頻學習資料

Python編程學習

查 看2022高級編程視頻教程免費獲取