寫在前面
最近項目需使用mongoose分頁操作,通過度娘發(fā)現(xiàn),很多都是直接用skip+limit實現(xiàn)分頁操作。但這樣做有個弊端,對于少量的數(shù)據(jù)來說,完全可行,當(dāng)數(shù)據(jù)量大了后,這種方案就不怎么可取了。因為skip需從頭開始查找,對于數(shù)據(jù)量大的數(shù)據(jù)庫,這樣的操作很費時間。當(dāng)然對于mysql等數(shù)據(jù)庫來說,分頁操作還有其他的實現(xiàn)方式,但單就mongodb數(shù)據(jù)庫來說,有個簡單的優(yōu)化方式就是通過ObjectId來實現(xiàn)。因為ObjectId是可以比較大小的(不清楚的可自行查看ObjectId的構(gòu)成方式),因此我們可以操作gt,lt來優(yōu)化我們的分頁效果。為什么說這樣會快一點呢?這樣操作,我們的查找是從_id數(shù)據(jù)開始查詢,減少遍歷數(shù)據(jù)的時間。
實現(xiàn)原理
首先,我們需要前端傳遞一個當(dāng)前頁數(shù)據(jù)的最后一個或第一個_id字段到服務(wù)器,服務(wù)器通過_id字段查找到數(shù)據(jù)所在位置,然后從當(dāng)前位置開始查找數(shù)據(jù),如果是向后翻頁,那么我們向后查詢limit個數(shù)據(jù)出來,如果向前翻頁,我們向前查limit個數(shù)據(jù)出來。
整個原理其實應(yīng)該不是很難的,但是在實際操作過程中,有個很蛋疼的問題~~~
實現(xiàn)過程
當(dāng)我們想明白了原理后,開始用代碼實現(xiàn):
因為踩了比較多的坑,我就直接記錄下最后一次正確的思路,其他的,不如為外人道也(啰嗦)。首先我們說前端需要上傳的參數(shù)(如圖1所示):id(記錄的本組數(shù)據(jù)第一個id或最后一個id), preNum(當(dāng)前所在頁 from), nextNum(將要前往的頁 to), limit(每組數(shù)據(jù)的數(shù)據(jù)量 2),至于為什么傳遞這幾個參數(shù)?請看下文。

假設(shè)數(shù)據(jù)庫中???5條數(shù)據(jù),編號為1-5,前端分頁,每頁limit(2)條數(shù)據(jù),那么我們的數(shù)據(jù)分組應(yīng)該是這樣的:
54,32,1
也就是說需要分三頁,為什么說需要傳遞第一個數(shù)據(jù)的id或者最后一條數(shù)據(jù)的_id呢?因為在我們從第一頁切換到第二頁的時候,拿到編號為2的數(shù)據(jù)id開始向后查找,若相鄰頁數(shù)跳轉(zhuǎn),就可直接向后拿到limit(2)條數(shù)據(jù),也就是我們的34數(shù)據(jù)。當(dāng)然,如果是第一頁直接跳第三頁,同理,我們可以直接從編號位2的數(shù)據(jù)開始向后跳過limit(2)條數(shù)據(jù),然后拿到編號為5的數(shù)據(jù)。正向跳轉(zhuǎn)解決完成。(ps:數(shù)據(jù)是反向獲取的,也就是最新的數(shù)據(jù)在最前面)
但是反向跳轉(zhuǎn)呢?我們還是可以使用類似的思想來操作。但是要注意,反向跳轉(zhuǎn)時,我們需要獲取每組數(shù)據(jù)的第一條id數(shù)據(jù)!也就是說假如我們要從第三頁跳轉(zhuǎn)到第二頁,我們就可以從1開始查找比1的_id大的數(shù)據(jù),那肯定會返回(5,4,3,2)四條數(shù)據(jù),這個時候如果我們按照從大到小的順序排列,我們的數(shù)據(jù)是這樣的:
1,54,32
和上面的數(shù)據(jù)比較會發(fā)現(xiàn),想要從第三頁跳轉(zhuǎn)到第二頁需要先跳過54兩條數(shù)據(jù),也就是說如果數(shù)據(jù)多了,其實還是從頭開始查詢的,對我們的優(yōu)化不友好。然后我們按照從小到大的順序排列呢?數(shù)據(jù)是這樣的:
1,23,45
我們再和開始的數(shù)據(jù)對比,會發(fā)現(xiàn)我們的數(shù)據(jù)是反著的,我們想要的數(shù)據(jù)是:1,32,54。這個時候我想到了一個解決辦法:假設(shè)從第三頁跳轉(zhuǎn)第二頁。我們拿到從前端傳過來的每組數(shù)據(jù)的第一條_id字段,這個時候我們拿到數(shù)據(jù)1的_id,然后向后查找limit(2)條數(shù)據(jù),這個時候?qū)⒎祷厍岸说臄?shù)據(jù)是23,這個時候我們可以在返回前做個處理,將獲取到的數(shù)據(jù)重新按照從大到小的順序排列數(shù)據(jù),將數(shù)據(jù)變?yōu)?2,這個時候前端拿到的第一個數(shù)據(jù)就是3,符合我們的數(shù)據(jù)要求,當(dāng)從第二頁向第一頁切換時,我們拿到的將是數(shù)據(jù)3的_id字段,重復(fù)前面的操作,從數(shù)據(jù)3的_id開始查找,會找到45兩條數(shù)據(jù),返回前都經(jīng)過重新排序。當(dāng)然,如果是跳過多頁,我們就只需要計算從_id開始跳過相關(guān)數(shù)據(jù)就行了,這樣就會大大減小數(shù)據(jù)的查詢等操作(代碼實現(xiàn)請看圖2)。

相關(guān)說明
- query對象是mongoose的方法,若使用mongodb類似,只需要將參數(shù)寫到find()內(nèi)就行
- ObjectId 重新排序是根據(jù)時間的高低重排??梢钥吹狡鋵峗id字段其實是有時間戳在里面的,我們使用getTimestamp()方法就可以解析出每個_id的生成時間。
收獲:
本次探究讓我更加深入的了解了mongodb做分頁時的過程,對于query參數(shù)的運作有了深入的理解,而在數(shù)組排序方面,學(xué)習(xí)到了自定義字段排序的方法,對數(shù)組排序有了更深入的了解。
ps:
以上內(nèi)容僅為自己的學(xué)習(xí)過程,歡迎大家取其精華,丟其糟粕。若對以上內(nèi)容有不同理解或更好的想法,歡迎一起探討。
企鵝號:1041415167 郵箱地址:zth1041415167@outlook.com