MongoDB分頁查詢的方法及效能

2021-06-28 01:04:30 字數 4601 閱讀 9067

自從上次redis之後呢,算是對nosql型別的產品有些入門了,這會換個方向,研究下真正的nosql資料庫——mongodb。說起mongodb,確實是用完了之後顛覆了我的資料管和程式觀。怎麼說呢?如果用在oo設計的程式裡那真的太棒了,像我這種資料驅動、表驅動思想根深蒂固的人,思路很難一下子跟上mongodb的節奏。當然並不是呼叫個api,寫幾句query那些思路,而是程式設計思路,業務領域的設計,如果oo,如何適合展現,適合查詢,適合聚合運算等等。總之mongodb重要的是程式的設計,設計好了,其實完全就忽略了mongo的儲存,因為mongodb實在是太方便了。

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

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

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

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

public class test

/// /// 客戶編號

///

[bsonelement("cust_id")]

public string customerid

/// /// 總數

///

[bsonelement("amount")]

public int amount

/// /// 狀態

///

[bsonelement("status")]

public string status

}

以下的操作基於mongodb gui 工具見參考資料3

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

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

query.where(***...***).skip(pageindex*pagesize).take(pagesize)
查詢了資料,還真有skip函式,而且還有limit函式 見參考資料1、2,於是輕易地實現了這樣的分頁查詢:

db.test.find().sort().skip(10).limit(10)//這裡忽略掉查詢語句
相當的高效,幾乎是幾毫秒就出來了結果,果然是nosql效率一流。但是慢,我這裡使用的資料只是10條而已,並沒有很多資料。我把資料加到100000,效率大概是20ms。如果這麼簡單就研究結束了的話,那真的是太辜負了程式猿要鑽研的精神了。sql分頁的方案,方案可是能有一大把,效率也是不一的,那mongo難道就這一種,答案顯然不是這樣的。另外是否效率上,效能上會有問題呢?redis篇裡,就吃過這樣的虧,亂用keys。

在檢視了一些資料之後,發現所有的資料都是這樣說的:

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

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

這裡我們假設查詢第100001條資料,這條資料的amount值是:2399927,我們來寫兩條語句分別如下:

db.test.sort().skip(100000).limit(10)  //183ms

db.test.find(}).sort().limit(10) //53ms

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

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

本篇文章裡使用的是官方的bson驅動,詳見參考資料4。mongo驅動附帶了另種方式一種是類似ado.net的原生query,一種是linq,這裡我們兩種都實現

方案一:條件查詢 原生query實現

var query = query.gt(item => item.amount, 2399927);                

var result = collection.find(query).setlimit(100)

.setsortorder(sortby.ascending("amount")).tolist();

console.writeline(result.first().tojson());//bson自帶的tojson

方案二:skip原生query實現

var result = collection.findall().setskip(100000).setlimit(100)

.setsortorder(sortby.ascending("amount"));

console.writeline(result.tolist().first().tojson());

方案三:linq 條件查詢

var result = collection.asqueryable().orderby(item => item.amount)

.where(item => item.amount > 2399927).take(100);

console.writeline(result.first().tojson());

方案四:linq skip版本

var result = collection.asqueryable().orderby(item => item.amount).skip(100000).take(100);

console.writeline(result.first().tojson());

這裡的測試**稍後我上傳一下,具體的實現是利用了老趙(我的偶像啊~)的codetimer

來計算效能。另外我跑**都是用testdriven

外掛程式來跑的。

方案一:

pagination gt-limit

time elapsed: 1,322ms

cpu cycles: 4,442,427,252

gen 0: 0

gen 1: 0

gen 2: 0

方案二:

pagination skip-limit

time elapsed: 95ms

cpu cycles: 18,280,728

gen 0: 0

gen 1: 0

gen 2: 0

方案三:

paginatilinq on linq where

time elapsed: 76ms

cpu cycles: 268,734,988

gen 0: 0

gen 1: 0

gen 2: 0

方案四:

pagination linq skip

time elapsed: 97ms

cpu cycles: 30,834,648

gen 0: 0

gen 1: 0

gen 2: 0

上面結果是不是大跌眼鏡,這和理論實在相差太大,第一次為什麼和後面的差距如此大?剛開始我以為是c# mongo的驅動問題,嘗試了換驅動也差不多。這幾天我在看《mongodb in action》的時候,發現文章裡提到:

mongodb會根據查詢,來載入文件的索引和元資料到記憶體裡,並且建議文件元資料的大小始終要保持小於機器記憶體,否則效能會下降。

注意到了上面的理論之後,我替換了我的測試方案,第一次執行排除下,然後再比較,發現確實結果正常了。

方案一的修正結果:

pagination gt-limit

time elapsed: 18ms

cpu cycles: 54,753,796

gen 0: 0

gen 1: 0

gen 2: 0

1. mongodb skip函式:

2. mongodb limit函式:

3. mongovue windows客戶端管理工具(有收費版本):

MongoDB 分頁查詢

之前在做mongo資料庫時,查詢資料庫中資料時需要分頁查詢,而我在採用分頁查詢時每次的查詢語句是使用skip 和limit 來這樣操作。但是當資料量達到一定程度時,可以發現查詢效率顯著下降,主要是因為這種方法是通過查詢時進行了乙個遍歷才skip的,所以隨著資料量的增加,查詢時間也是線性的增加的。比如...

mongodb查詢方法

集合簡單查詢方法 mongodb語法 db.collection.find collection就是集合的名稱,這個可以自己進行建立。對比sql語句 select from collection 查詢集合中所有的文件,即關係型資料庫中的查詢表中的所有資料。返回制定的鍵值 mongodb語法 db.c...

MongoDB動態條件之分頁查詢

1.繼承mongorepository public inte ce studentrepository extends mongorepository2.實現 二 mongotemplate結合query 實現一 使用criteria封裝查詢條件 public pagegetlistwithcri...