Mongo не оптимизирует $ или запрос, комбинируя два IXSCAN

У меня есть orders коллекция со следующим индексом, среди прочего:

{location: 1, completedDate: 1, estimatedProductionDate: 1, estimatedCompletionDate: 1}

Я выполняю следующий запрос:

db.orders.find({
  status: {$in: [1, 2, 3]},
  location: "PA",
  $or: [
    {completedDate: {$lt: ISODate("2017-08-22T04:59:59.999Z")}},
    {
      completedDate: null,
      estimatedProductionDate: {$lt: ISODate("2017-08-22T04:59:59.999Z")}
    }
  ]
}).explain()

Я надеялся, что это будет эффективно IXSCAN для каждой ветви $or, а затем объединить результаты:

        {completedDate: {$lt: ISODate("2017-08-22T04:59:59.999Z")}}

        "indexBounds" : {
            "location" : [
                "[\"TX\", \"TX\"]"
            ],
            "completedDate" : [
                "[MinKey, ISODate("2017-08-22T04:59:59.999Z")]"
            ],
            "estimatedProductionDate" : [
                "[MinKey, MaxKey]"
            ],
            "estimatedCompletionDate" : [
                "[MinKey, MaxKey]"
            ]
        }

        {
            completedDate: null,
            estimatedProductionDate: {$lt: ISODate("2017-08-22T04:59:59.999Z")}
        }

        "indexBounds" : {
            "location" : [
                "[\"TX\", \"TX\"]"
            ],
            "completedDate" : [
                "[null, null]"
            ],
            "estimatedProductionDate" : [
                "[MinKey, ISODate("2017-08-22T04:59:59.999Z")]"
            ],
            "estimatedCompletionDate" : [
                "[MinKey, MaxKey]"
            ]
        }

Вместо этого это только ограничивает location в IXSCANи выполняет остальную часть фильтрации во время FETCH, Есть ли способ оптимизировать этот запрос, не разбивая его на два отдельных запроса?

"winningPlan" : {
    "stage" : "FETCH",
    "filter" : {
        "$and" : [
            {
                "$or" : [
                    {
                        "$and" : [
                            {
                                "completedDate" : {
                                    "$eq" : null
                                }
                            },
                            {
                                "estimatedProductionDate" : {
                                    "$lt" : "2017-08-22T04:59:59.999Z"
                                }
                            }
                        ]
                    },
                    {
                        "completedDate" : {
                            "$lt" : "2017-08-22T04:59:59.999Z"
                        }
                    }
                ]
            },
            {
                "status" : {
                    "$in" : [
                        1,
                        2,
                        3
                    ]
                }
            }
        ]
    },
    "inputStage" : {
        "stage" : "IXSCAN",
        "keyPattern" : {
            "location" : 1,
            "completedDate" : 1,
            "estimatedProductionDate" : 1,
            "estimatedCompletionDate" : 1
        },
        "indexName" : "location_1_completedDate_1_estimatedProductionDate_1_estimatedCompletionDate_1",
        "isMultiKey" : false,
        "isUnique" : false,
        "isSparse" : false,
        "isPartial" : false,
        "indexVersion" : 1,
        "direction" : "forward",
        "indexBounds" : {
            "location" : [
                "[\"TX\", \"TX\"]"
            ],
            "completedDate" : [
                "[MinKey, MaxKey]"
            ],
            "estimatedProductionDate" : [
                "[MinKey, MaxKey]"
            ],
            "estimatedCompletionDate" : [
                "[MinKey, MaxKey]"
            ]
        }
    }
},

1 ответ

Решение

Есть три проблемы, которые сразу бросаются в глаза:

Ваш индекс

Я не уверен насчет других ваших индексов, но ваш запрос имеет форму:

{
  status:1,
  location:1,
  $or: [
    {completedDate:1},
    {completedDate:1, estimatedProductionDate:1}
  ]
}

Однако ваш индекс не содержит термин status, Вам понадобится status поле в вашем индексе, чтобы максимально использовать индекс.

Ваш $ или запрос

Перефразируя страницу $ или "Статьи и индексы":

... чтобы MongoDB использовал индексы для вычисления выражения $ или, все выражения в выражении $ или должны поддерживаться индексами. В противном случае MongoDB выполнит сканирование коллекции.

Проще говоря, эффективно $or запросы в MongoDB потребуют $or термин будет термином верхнего уровня, причем каждая часть термина поддерживается индексом.

Например, вам может показаться, что производительность следующего индекса и запроса немного выше:

db.orders.createIndex({
  status:1,
  location:1,
  completedDate:1,
  estimatedProductionDate:1
})

db.orders.explain().find({
  $or: [
    {
      status: {$in: [1, 2, 3]},
      location: "PA",
      completedDate: {$lt: ISODate("2017-08-22T04:59:59.999Z")}},
    {
      status: {$in: [1, 2, 3]},
      location: "PA",
      completedDate: null,
      estimatedProductionDate: {$lt: ISODate("2017-08-22T04:59:59.999Z")}
    }
  ]
})

Причина в том, что MongoDB рассматривает каждый термин в $or запрос должен быть отдельным запросом. Таким образом, каждый термин может использовать свой собственный индекс.

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

Однако, это все еще не оптимально, потому что MongoDB должен выполнить выборку с filter: {completedDate: {$eq: null}} после сканирования индекса для запроса с completedDate: null, Причина этого тонкая и лучше всего объясняется здесь:

  1. Документ {} генерирует ключ индекса {"": null} для индекса с шаблоном ключа {"ab": 1}.
  2. Документ {a: []} также генерирует индексный ключ {"": null} для индекса с шаблоном ключа {"ab": 1}.
  3. Документ {} соответствует запросу {"ab": null}.
  4. Документ {a: []} не соответствует запросу {"ab": null}.

Следовательно, запрос {"ab": null}, на который отвечает индекс с шаблоном ключа {"ab": 1}, должен извлечь документ и перепроверить предикат, чтобы убедиться, что документ {} включен в набор результатов и то, что документ {a: []} не включен в набор результатов.

Чтобы максимально использовать индекс, вам может быть лучше просто назначить что-то в completedDate поле вместо того, чтобы установить его null,

Другие вопросы по тегам