【问题标题】:MongoDB: Strange behaviour of sort with pagination leading to duplicatesMongoDB:分页导致重复的奇怪排序行为
【发布时间】:2021-11-15 23:11:27
【问题描述】:

在这里,我刚刚创建了一个包含 9 个文档 {id, name} 的集合。 所有文档都具有相同的字段name的值“A”。

[
  {
    "name": "A",
    "id": 1
  },
  {
    "name": "A",
    "id": 2
  },
  {
    "name": "A",
    "id": 3
  },
  {
    "name": "A",
    "id": 4
  },
  {
    "name": "A",
    "id": 5
  },
  {
    "name": "A",
    "id": 6
  },
  {
    "name": "A",
    "id": 7
  },
  {
    "name": "A",
    "id": 8
  },
  {
    "name": "A",
    "id": 9
  }
]

我想在按名称排序后对这个集合进行分页(在我的情况下按名称排序是没有用的,但我这样做是为了证明奇怪的行为),3 x 3。(页面大小为 3)。

当我使用 $skip 0, $limit 3(第一页)执行聚合管道时:

db.collection.aggregate([
  {
    "$sort": {
      "name": 1
    }
  },
  {
    "$skip": 0
  },
  {
    "$limit": 3
  }
])

结果是:

[
  {
    "_id": ObjectId("5a934e000102030405000000"),
    "id": 1,
    "name": "A"
  },
  {
    "_id": ObjectId("5a934e000102030405000001"),
    "id": 2,
    "name": "A"
  },
  {
    "_id": ObjectId("5a934e000102030405000002"),
    "id": 3,
    "name": "A"
  }
]

而现在当我想得到以下页面时($skip 3, $limit 3),结果是:

[
  {
    "_id": ObjectId("5a934e000102030405000000"),
    "id": 1,
    "name": "A"
  },
  {
    "_id": ObjectId("5a934e000102030405000002"),
    "id": 3,
    "name": "A"
  },
  {
    "_id": ObjectId("5a934e000102030405000005"),
    "id": 6,
    "name": "A"
  }
]

我们注意到 id 为 1 和 3 的文档被再次提取。这最终会导致分页错误(重复)!

当对非唯一列进行排序导致这种奇怪行为时,您如何解释这一点?

重现问题https://mongoplayground.net/p/hP7CMtA3b2f

【问题讨论】:

    标签: mongodb sorting pagination duplicates aggregation-framework


    【解决方案1】:

    当你运行时:sort -> skip(3) -> limit(3)

    Mongo 查询优化器将查询顺序更改为:sort + limit(6) -> skip(3)

    所以 mongo 正在使用 'limit+skip' 的值创建一个 'limit'。而且sort和limit不是两个不同的阶段,而是一个阶段。而这种查询优化的原因是找到最大的 6 个元素并对其进行排序,而不是首先对所有元素进行排序并获得前 6 个元素。

    因此出现了这种奇怪的行为。证明解释结果:

    {
        "stages" : [ 
            {
                "$cursor" : {
                    "query" : {},
                    "queryPlanner" : {
                        "plannerVersion" : 1,
                        "namespace" : "5ceb96f75538551e7d3bcdb8_lav.test",
                        "indexFilterSet" : false,
                        "parsedQuery" : {},
                        "winningPlan" : {
                            "stage" : "COLLSCAN",
                            "direction" : "forward"
                        },
                        "rejectedPlans" : []
                    }
                }
            }, 
            {
                "$sort" : {
                    "sortKey" : {
                        "name" : 1
                    },
                    "limit" : NumberLong(6)
                }
            }, 
            {
                "$skip" : NumberLong(3)
            }
        ],
        "ok" : 1.0,
        "operationTime" : Timestamp(1560596833, 1),
        "$clusterTime" : {
            "clusterTime" : Timestamp(1560596835, 4),
            "signature" : {
                "hash" : { "$binary" : "ouhjbA5FjqF/EE4ySVpHdvG8HaM=", "$type" : "00" },
                "keyId" : NumberLong(6691284195030859777)
            }
        }
    }
    

    在名称字段上添加索引,排序将变为稳定排序。你会得到想要的结果。

    【讨论】:

    • 而这种查询优化的原因是找到最大的 6 个元素并对其进行排序,而不是首先对所有元素进行排序并获得前 6 个元素。
    • 但是当我对唯一的 ID 进行排序时,我没有问题。奇怪的行为来自对不唯一的列进行排序。
    • 是的,因为它们都是独一无二的。但在这种情况下,一切都是平等的。尝试对名称和限制 6 进行排序查询。您不会得到排序列表(基于 id)。
    • 在name字段上添加索引,排序会变成稳定排序。你会得到想要的结果。
    猜你喜欢
    • 1970-01-01
    • 2018-11-22
    • 2020-06-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-27
    • 2011-03-13
    • 1970-01-01
    相关资源
    最近更新 更多