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
, Причина этого тонкая и лучше всего объясняется здесь:
- Документ {} генерирует ключ индекса {"": null} для индекса с шаблоном ключа {"ab": 1}.
- Документ {a: []} также генерирует индексный ключ {"": null} для индекса с шаблоном ключа {"ab": 1}.
- Документ {} соответствует запросу {"ab": null}.
- Документ {a: []} не соответствует запросу {"ab": null}.
Следовательно, запрос {"ab": null}, на который отвечает индекс с шаблоном ключа {"ab": 1}, должен извлечь документ и перепроверить предикат, чтобы убедиться, что документ {} включен в набор результатов и то, что документ {a: []} не включен в набор результатов.
Чтобы максимально использовать индекс, вам может быть лучше просто назначить что-то в completedDate
поле вместо того, чтобы установить его null
,