mongodb 使用聚合aggregate 排序後分頁結果順序不一致問題的解決辦法及處理過程 解決數據重複

結論

先上結論,在不建立索引的情況下,除使用 _id 之外的字段排序,使用 find 進行分頁查詢 skiplimit 的查詢結果能正常返回順序,不會受到其他影響。而使用聚合方式 aggregate 查詢,其返回結果是不確定的,解決方式是爲這個字段建立索引或者增加能確定順序的排序條件。
在這裏插入圖片描述

原因

因爲在這種查詢中,提供的排序條件是不充分的。在進行聚合查詢時,數據庫已經按照要求按 {count: 1} 完成了排序,但是因爲它們的值都一樣,不管誰放在前面誰放在後面其實都沒有違反要求,因爲查詢要求只是 count 的升序而已。從數據庫的角度來講,既然沒有額外的要求,那當然是以最高效的方式返回結果,也就是不管 count 以外的順序,因爲這樣最省資源。
那麼什麼是最高效?這裏涉及一些數據庫底層的知識。在單機上,如果沒有索引支持,數據庫會嘗試遍歷所有數據,然後做一個內存排序來返回結果,從節省資源的角度,顯然這個排序只排到滿足了 count 升序爲止,其他字段可以說是先到先得。這就造成在count相同時,其他順序是隨機的。它們可能受到:

  1. 自己在磁盤上的順序影響,因爲這會影響到數據庫先遍歷到哪條記錄。並且要注意,每次更新數據時它們在磁盤上的順序是會變化的;
  2. 在分片集羣環境中,結果還受到哪個片先返回數據的影響。分片環境中的排序是先在各個片排好序,再進行一次合併排序;
  3. 理論上還和數據庫使用的排序算法相關;

總之,在 aggregate 查詢中,如果不指定,數據庫不保證返回結果是確定的。
至於解決方案也已經很明確了,指定一個可以完全確定順序的排序條件,比如:
{$sort: {count: -1, _id: 1}}
但是需要知道的是,這樣會讓數據庫付出額外的操作來保證第二個排序條件的正確性,在實際使用場景中要根據實際情況判斷這是不是真的有意義。
另外的方式是給這個字段建立索引,那麼查詢出來的結果就會按照索引中的順序返回。

測試過程:

生成20條測試數據

for (let i = 0 ; i < 10 ; i++){
    db.getCollection('Test').insert({name:String(i), count:NumberInt(0)})
}

結果如下:

db.getCollection('Test').find({})

在這裏插入圖片描述

find 查詢分頁的結果

db.getCollection('Test').find({}).sort({count:1}).limit(3)

在這裏插入圖片描述

db.getCollection('Test').find({}).sort({count:1}).limit(5)

在這裏插入圖片描述
可以看到返回的結果是確定的,不會因爲 skiplimit 而受影響。

aggregate 查詢分頁的結果

db.getCollection('Test').aggregate([
    {$sort: {count:1}},
    {$limit:3}
])

在這裏插入圖片描述

db.getCollection('Test').aggregate([
    {$sort: {count:1}},
    {$limit:5}
])

在這裏插入圖片描述

db.getCollection('Test').aggregate([
    {$sort: {count:1}},
    {$limit:10}
])

在這裏插入圖片描述

db.getCollection('Test').aggregate([
    {$sort: {count:1}},
    {$skip: 1},
    {$limit:5}
])

在這裏插入圖片描述
在這裏我們可以看到,返回結果是不確定的,會隨着 skiplimit 而受影響。
增加一個排序字段,再次進行查詢,能看到結果符合預期,不會受到影響。

db.getCollection('Test').aggregate([
    {$sort: {count:1,_id:1}},
    {$limit:3}
])

在這裏插入圖片描述

創建字段索引

這時候我們給這個創建索引,看看情況怎樣

db.getCollection('Test').createIndex({"count":1})

在這裏插入圖片描述
在這裏插入圖片描述
再次進行查詢,看看結果如何

db.getCollection('Test').aggregate([
    {$sort: {count:1}},
    {$limit:10}
])

在這裏插入圖片描述
此時已經發現,返回結果是跟 find 查詢一致,並且不會受到 skiplimit 的影響。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章