Почему Mongo запрашивает нулевые фильтры в FETCH после выполнения IXSCAN

Согласно документации Mongo,

Запрос { item : null } соответствует документам, которые либо содержат поле item со значением null, либо не содержат поля item.

Я не могу найти документацию для этого, но, насколько я могу судить, оба случая (значение null или поле отсутствует) хранятся в индексе как null.

Поэтому, если я выполню db.orders.createIndex({item: 1}), а затем db.orders.find({item: null}), я ожидаю, что IXSCAN найдет все документы, которые либо содержат поле item со значением null, либо не содержат поля item, и только эти документы.

Тогда почему db.orders.find({item: null}).explain() выполняет filter: {item: {$eq: null}} на этапе FETCH после выполнения IXSCAN? Какие возможные документы необходимо отфильтровать?

{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "temp.orders",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "item" : {
                "$eq" : null
            }
        },
        "winningPlan" : {
            "stage" : "FETCH",
            "filter" : {
                "item" : {
                    "$eq" : null
                }
            },
            "inputStage" : {
                "stage" : "IXSCAN",
                "keyPattern" : {
                    "item" : 1
                },
                "indexName" : "item_1",
                "isMultiKey" : false,
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 1,
                "direction" : "forward",
                "indexBounds" : {
                    "item" : [
                        "[null, null]"
                    ]
                }
            }
        },
        "rejectedPlans" : [ ]
    },
    "serverInfo" : {
        "host" : "Andys-MacBook-Pro-2.local",
        "port" : 27017,
        "version" : "3.2.8",
        "gitVersion" : "ed70e33130c977bda0024c125b56d159573dbaf0"
    },
    "ok" : 1
}

Я думал, что, возможно, значения undefined будут проиндексированы как null, но простой эксперимент исключает это:

> db.orders.createIndex({item: 1})
{
    "createdCollectionAutomatically" : true,
    "numIndexesBefore" : 1,
    "numIndexesAfter" : 2,
    "ok" : 1
}
> db.orders.insert({item: undefined})
WriteResult({ "nInserted" : 1 })
> db.orders.find({item: {$type: 6}}).explain()
{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "temp.orders",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "item" : {
                "$type" : 6
            }
        },
        "winningPlan" : {
            "stage" : "FETCH",
            "filter" : {
                "item" : {
                    "$type" : 6
                }
            },
            "inputStage" : {
                "stage" : "IXSCAN",
                "keyPattern" : {
                    "item" : 1
                },
                "indexName" : "item_1",
                "isMultiKey" : false,
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 1,
                "direction" : "forward",
                "indexBounds" : {
                    "item" : [
                        "[undefined, undefined]"
                    ]
                }
            }
        },
        "rejectedPlans" : [ ]
    },
    "serverInfo" : {
        "host" : "Andys-MacBook-Pro-2.local",
        "port" : 27017,
        "version" : "3.2.8",
        "gitVersion" : "ed70e33130c977bda0024c125b56d159573dbaf0"
    },
    "ok" : 1
}

person Andy    schedule 23.08.2017    source источник


Ответы (1)


Семантика для предиката совпадения нулевого равенства (например, {"a.b": null}) достаточно сложна, потому что поле может содержать вложенные документы, для которых одного сканирования индекса недостаточно для получения правильного результата.

Согласно https://jira.mongodb.org/browse/SERVER-18653?focusedCommentId=931817&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-931817,

В версии 2.6.0 сервера изменена семантика предиката соответствия нулевого равенства, так что документ {a: []} больше не считался совпадением для предиката запроса {"a.b": null} (в предыдущих версиях сервер, этот документ считается соответствующим этому предикату). Это задокументировано в примечаниях о совместимости 2.6 в разделе «сравнение с нулевым значением».

Для индекса с шаблоном ключа {"a.b": 1} этот документ {a: []} генерирует ключ индекса {"": null}. Другие документы, такие как {a: null} и пустой документ {}, также генерируют ключ индекса {"": null}. В результате, если запрос с предикатом {"a.b": null} использует этот индекс, система запросов не может определить только по ключу индекса {"": null}, соответствует ли связанный документ предикату. В результате вместо границ EXACT назначаются границы INEXACT_FETCH, и, следовательно, в дерево выполнения запроса добавляется этап FETCH.

Дополнительное объяснение:

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

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

person Andy    schedule 23.08.2017