MongoDB: Странное поведение сортировки с разбиением на страницы, приводящее к дублированию - PullRequest
1 голос
/ 15 июня 2019

Здесь я только что создал коллекцию из 9 документов {id, name}.Все документы имеют одинаковое значение «A» поля name .

[
  {
    "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 на 3. (размер страницы - 3).

Когда я выполняю агрегатный конвейер с $ skip 0, $предел 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"
  }
]

Мы отмечаем, что документы с идентификаторами 1 и 3 извлекаются снова.Это заканчивается плохой нумерацией страниц (дубликаты)!

Как вы это объясните, когда сортировка по неуникальному столбцу приводит к этому странному поведению?

Воспроизвести задачу https://mongoplayground.net/p/hP7CMtA3b2f

1 Ответ

0 голосов
/ 15 июня 2019

Когда вы запускаете: sort -> skip (3) -> limit (3)

Оптимизатор запросов Mongo изменяет порядок запросов на: sort + limit (6) -> skip (3)

Таким образом, монго устанавливает «предел» со значением «предел + пропуск». И сортировка и ограничение - это не два разных этапа, а один этап. И причина этой оптимизации запросов заключается в том, что лучше найти 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)
        }
    }
}

Добавьте индекс в поле имени, сортировка превратится в стабильную сортировку. И вы получите желаемый результат.

...