MongoDB:分页排序的奇怪行为导致重复

hzi*_*oun 5 sorting pagination duplicates mongodb aggregation-framework

在这里,我刚刚创建了 9 个文档 {id, 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
  }
]
Run Code Online (Sandbox Code Playgroud)

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

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

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

结果是:

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

现在,当我想要获取以下页面($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"
  }
]

Run Code Online (Sandbox Code Playgroud)

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

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

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

Raj*_*oel 3

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

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

所以 mongo 正在使用“limit+skip”的值进行“限制”。并且排序和限制不是两个不同的阶段,而是一个阶段。这种查询优化的原因是找到最大的 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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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