Монго отказывается использовать все поля из составного индекса

У меня большая база данных и несколько составных индексов. Проблема в том, что Mongo не использует все поля из индекса для запроса, он использует только 2 поля из 4, хотя все 4 поля находятся в составном индексе. Или он использует составной индекс из 3 полей вместо 4-х полей.

Вот моя схема (есть другие поля в данных и мета, но они не используются в этом запросе):

{
    store_id: {type: String},
    data: {
        id: {type: Number},
    },
    meta: {
        is_published: {type: Boolean},
        lowercase_sku: {type: String}
    }
}

У меня есть эти индексы:

db.products.createIndex({'store_id':1,'meta.is_published':1,'data.id':1})
db.products.createIndex({'store_id':1,'meta.is_published':1,'data.id':1,'meta.lowercase_sku':1})

Вот мой запрос:

db.products.find({
    'store_id': 'my_domain.com',
    'meta.lowercase_sku': 'go-a83-3034/8+4',
    'meta.is_published': true, 
    'data.id': {'$ne': 7801040323}
}).explain('executionStats')

И вот что я получаю из объяснения:

{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "my_database.products",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "$and" : [
                {
                    "meta.is_published" : {
                        "$eq" : true
                    }
                },
                {
                    "meta.lowercase_sku" : {
                        "$eq" : "go-a83-3034/8+4"
                    }
                },
                {
                    "store_id" : {
                        "$eq" : "my_domain.com"
                    }
                },
                {
                    "$not" : {
                        "data.id" : {
                            "$eq" : 7801040323
                        }
                    }
                }
            ]
        },
        "winningPlan" : {
            "stage" : "FETCH",
            "filter" : {
                "meta.lowercase_sku" : {
                    "$eq" : "go-a83-3034/8+4"
                }
            },
            "inputStage" : {
                "stage" : "IXSCAN",
                "keyPattern" : {
                    "store_id" : 1,
                    "meta.is_published" : 1,
                    "data.id" : 1
                },
                "indexName" : "store_id_1_meta.is_published_1_data.id_1",
                "isMultiKey" : false,
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 1,
                "direction" : "forward",
                "indexBounds" : {
                    "store_id" : [
                        "[\"my_domain.com\", \"my_domain.com\"]"
                    ],
                    "meta.is_published" : [
                        "[true, true]"
                    ],
                    "data.id" : [
                        "[MinKey, 7801040323.0)",
                        "(7801040323.0, MaxKey]"
                    ]
                }
            }
        },
        "rejectedPlans" : [
            {
                "stage" : "FETCH",
                "filter" : {
                    "$and" : [
                        {
                            "$not" : {
                                "data.id" : {
                                    "$eq" : 7801040323
                                }
                            }
                        },
                        {
                            "meta.lowercase_sku" : {
                                "$eq" : "go-a83-3034/8+4"
                            }
                        }
                    ]
                },
                "inputStage" : {
                    "stage" : "IXSCAN",
                    "keyPattern" : {
                        "store_id" : 1,
                        "meta.is_published" : 1,
                        "data.id" : 1,
                        "meta.lowercase_sku" : 1
                    },
                    "indexName" : "store_id_1_meta.is_published_1_data.id_1_meta.lowercase_sku_1",
                    "isMultiKey" : true,
                    "isUnique" : false,
                    "isSparse" : false,
                    "isPartial" : false,
                    "indexVersion" : 1,
                    "direction" : "forward",
                    "indexBounds" : {
                        "store_id" : [
                            "[\"my_domain.com\", \"my_domain.com\"]"
                        ],
                        "meta.is_published" : [
                            "[true, true]"
                        ],
                        "data.id" : [
                            "[MinKey, 7801040323.0)",
                            "(7801040323.0, MaxKey]"
                        ],
                        "meta.lowercase_sku" : [
                            "[MinKey, MaxKey]"
                        ]
                    }
                }
            }
        ]
    },
    "executionStats" : {
        "executionSuccess" : true,
        "nReturned" : 1,
        "executionTimeMillis" : 4590,
        "totalKeysExamined" : 28527,
        "totalDocsExamined" : 28525,
        "executionStages" : {
            "stage" : "FETCH",
            "filter" : {
                "meta.lowercase_sku" : {
                    "$eq" : "go-a83-3034/8+4"
                }
            },
            "nReturned" : 1,
            "executionTimeMillisEstimate" : 130,
            "works" : 28528,
            "advanced" : 1,
            "needTime" : 28525,
            "needYield" : 0,
            "saveState" : 5351,
            "restoreState" : 5351,
            "isEOF" : 1,
            "invalidates" : 0,
            "docsExamined" : 28525,
            "alreadyHasObj" : 0,
            "inputStage" : {
                "stage" : "IXSCAN",
                "nReturned" : 28525,
                "executionTimeMillisEstimate" : 70,
                "works" : 28527,
                "advanced" : 28525,
                "needTime" : 1,
                "needYield" : 0,
                "saveState" : 5351,
                "restoreState" : 5351,
                "isEOF" : 1,
                "invalidates" : 0,
                "keyPattern" : {
                    "store_id" : 1,
                    "meta.is_published" : 1,
                    "data.id" : 1
                },
                "indexName" : "store_id_1_meta.is_published_1_data.id_1",
                "isMultiKey" : false,
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 1,
                "direction" : "forward",
                "indexBounds" : {
                    "store_id" : [
                        "[\"my_domain.com\", \"my_domain.com\"]"
                    ],
                    "meta.is_published" : [
                        "[true, true]"
                    ],
                    "data.id" : [
                        "[MinKey, 7801040323.0)",
                        "(7801040323.0, MaxKey]"
                    ]
                },
                "keysExamined" : 28527,
                "dupsTested" : 0,
                "dupsDropped" : 0,
                "seenInvalidated" : 0
            }
        }
    },
    "serverInfo" : {
        "version" : "3.2.18",
        "gitVersion" : "4c1bae566c0c00f996a2feb16febf84936ecaf6f"
    },
    "ok" : 1
}

Как видите, по какой-то причине mongo не использует индекс из 4 полей, и даже если я намекаю на индекс, он использует только два первых поля ("store_id" и "meta.is_published"). Он имеет тенденцию выбирать индекс из 3 полей, который охватывает только 3 поля ("store_id", "meta.is_published" и "data.id"), что имеет смысл, при условии, что из индекса из 4 полей используются только 2 поля. Но почему?

Может кто-нибудь объяснить мне и, возможно, намекнуть, как я могу заставить запрос работать быстрее?

База данных большая (около 11 ГБ), и она была недавно перенесена (импортирована с индексами), если это поможет. Однако указанные индексы были созданы ПОСЛЕ импорта.

UPD (2): Я также создал тестовую коллекцию документов с той же схемой и индексами. И тот же запрос действительно использует индекс правильно. Поэтому я склонен думать, что существует какая-то проблема с размерами индекса или типами значений. Но я все еще не могу определить это.

Любая помощь или понимание этого я очень ценю.


Ниже приведен запрос с подсказкой к 4-полюсному индексу и результатом для вашей ссылки.

db.products.find({
    'store_id': 'my_domain.com',
    'meta.lowercase_sku': 'go-a83-3034/8+4',
    'meta.is_published': true, 
    'data.id': {'$ne': 7801040323}
}).hint('store_id_1_meta.is_published_1_data.id_1_meta.lowercase_sku_1').explain('executionStats')

Результат:

{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "sharp_production.products",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "$and" : [
                {
                    "meta.is_published" : {
                        "$eq" : true
                    }
                },
                {
                    "meta.lowercase_sku" : {
                        "$eq" : "go-a83-3034/8+4"
                    }
                },
                {
                    "store_id" : {
                        "$eq" : "my_domain.com"
                    }
                },
                {
                    "$not" : {
                        "data.id" : {
                            "$eq" : 7801040323
                        }
                    }
                }
            ]
        },
        "winningPlan" : {
            "stage" : "FETCH",
            "filter" : {
                "$and" : [
                    {
                        "$not" : {
                            "data.id" : {
                                "$eq" : 7801040323
                            }
                        }
                    },
                    {
                        "meta.lowercase_sku" : {
                            "$eq" : "go-a83-3034/8+4"
                        }
                    }
                ]
            },
            "inputStage" : {
                "stage" : "IXSCAN",
                "keyPattern" : {
                    "store_id" : 1,
                    "meta.is_published" : 1,
                    "data.id" : 1,
                    "meta.lowercase_sku" : 1
                },
                "indexName" : "store_id_1_meta.is_published_1_data.id_1_meta.lowercase_sku_1",
                "isMultiKey" : true,
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 1,
                "direction" : "forward",
                "indexBounds" : {
                    "store_id" : [
                        "[\"my_domain.com\", \"my_domain.com\"]"
                    ],
                    "meta.is_published" : [
                        "[true, true]"
                    ],
                    "data.id" : [
                        "[MinKey, 7801040323.0)",
                        "(7801040323.0, MaxKey]"
                    ],
                    "meta.lowercase_sku" : [
                        "[MinKey, MaxKey]"
                    ]
                }
            }
        },
        "rejectedPlans" : [ ]
    },
    "executionStats" : {
        "executionSuccess" : true,
        "nReturned" : 1,
        "executionTimeMillis" : 213,
        "totalKeysExamined" : 28630,
        "totalDocsExamined" : 28525,
        "executionStages" : {
            "stage" : "FETCH",
            "filter" : {
                "$and" : [
                    {
                        "$not" : {
                            "data.id" : {
                                "$eq" : 7801040323
                            }
                        }
                    },
                    {
                        "meta.lowercase_sku" : {
                            "$eq" : "go-a83-3034/8+4"
                        }
                    }
                ]
            },
            "nReturned" : 1,
            "executionTimeMillisEstimate" : 210,
            "works" : 28630,
            "advanced" : 1,
            "needTime" : 28628,
            "needYield" : 0,
            "saveState" : 223,
            "restoreState" : 223,
            "isEOF" : 1,
            "invalidates" : 0,
            "docsExamined" : 28525,
            "alreadyHasObj" : 0,
            "inputStage" : {
                "stage" : "IXSCAN",
                "nReturned" : 28525,
                "executionTimeMillisEstimate" : 110,
                "works" : 28630,
                "advanced" : 28525,
                "needTime" : 104,
                "needYield" : 0,
                "saveState" : 223,
                "restoreState" : 223,
                "isEOF" : 1,
                "invalidates" : 0,
                "keyPattern" : {
                    "store_id" : 1,
                    "meta.is_published" : 1,
                    "data.id" : 1,
                    "meta.lowercase_sku" : 1
                },
                "indexName" : "store_id_1_meta.is_published_1_data.id_1_meta.lowercase_sku_1",
                "isMultiKey" : true,
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 1,
                "direction" : "forward",
                "indexBounds" : {
                    "store_id" : [
                        "[\"my_domain.com\", \"my_domain.com\"]"
                    ],
                    "meta.is_published" : [
                        "[true, true]"
                    ],
                    "data.id" : [
                        "[MinKey, 7801040323.0)",
                        "(7801040323.0, MaxKey]"
                    ],
                    "meta.lowercase_sku" : [
                        "[MinKey, MaxKey]"
                    ]
                },
                "keysExamined" : 28630,
                "dupsTested" : 28628,
                "dupsDropped" : 103,
                "seenInvalidated" : 0
            }
        }
    },
    "serverInfo" : {
        "version" : "3.2.18",
        "gitVersion" : "4c1bae566c0c00f996a2feb16febf84936ecaf6f"
    },
    "ok" : 1
}

ОБНОВЛЕНИЕ (1)


Хорошо, очевидно, оператор $ ne может испортить индекс, как сказал @JohnnyHK. Итак, я создал еще один такой:

db.products.createIndex({'store_id':1,'meta.is_published':1,'meta.lowercase_sku':1})

... и попытался использовать его для моего запроса после удаления "$ne" (это всего лишь один документ, на самом деле ничего не меняет):

db.products.find({
    'store_id': 'my_domain.com',
    'meta.is_published': true,
    'meta.lowercase_sku': 'go-a83-3034/8+4',
}).hint('store_id_1_meta.is_published_1_meta.lowercase_sku_1').explain('executionStats')

Но вот что я получил на этот раз:

{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "sharp_production.products",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "$and" : [
                {
                    "meta.is_published" : {
                        "$eq" : true
                    }
                },
                {
                    "meta.lowercase_sku" : {
                        "$eq" : "go-a83-3034/8+4"
                    }
                },
                {
                    "store_id" : {
                        "$eq" : "my_domain.com"
                    }
                }
            ]
        },
        "winningPlan" : {
            "stage" : "FETCH",
            "filter" : {
                "meta.lowercase_sku" : {
                    "$eq" : "go-a83-3034/8+4"
                }
            },
            "inputStage" : {
                "stage" : "IXSCAN",
                "keyPattern" : {
                    "store_id" : 1,
                    "meta.is_published" : 1,
                    "meta.lowercase_sku" : 1
                },
                "indexName" : "store_id_1_meta.is_published_1_meta.lowercase_sku_1",
                "isMultiKey" : true,
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 1,
                "direction" : "forward",
                "indexBounds" : {
                    "store_id" : [
                        "[\"my_domain.com\", \"my_domain.com\"]"
                    ],
                    "meta.is_published" : [
                        "[true, true]"
                    ],
                    "meta.lowercase_sku" : [
                        "[MinKey, MaxKey]"
                    ]
                }
            }
        },
        "rejectedPlans" : [ ]
    },
    "executionStats" : {
        "executionSuccess" : true,
        "nReturned" : 1,
        "executionTimeMillis" : 136,
        "totalKeysExamined" : 28629,
        "totalDocsExamined" : 28526,
        "executionStages" : {
            "stage" : "FETCH",
            "filter" : {
                "meta.lowercase_sku" : {
                    "$eq" : "go-a83-3034/8+4"
                }
            },
            "nReturned" : 1,
            "executionTimeMillisEstimate" : 120,
            "works" : 28630,
            "advanced" : 1,
            "needTime" : 28628,
            "needYield" : 0,
            "saveState" : 223,
            "restoreState" : 223,
            "isEOF" : 1,
            "invalidates" : 0,
            "docsExamined" : 28526,
            "alreadyHasObj" : 0,
            "inputStage" : {
                "stage" : "IXSCAN",
                "nReturned" : 28526,
                "executionTimeMillisEstimate" : 30,
                "works" : 28630,
                "advanced" : 28526,
                "needTime" : 103,
                "needYield" : 0,
                "saveState" : 223,
                "restoreState" : 223,
                "isEOF" : 1,
                "invalidates" : 0,
                "keyPattern" : {
                    "store_id" : 1,
                    "meta.is_published" : 1,
                    "meta.lowercase_sku" : 1
                },
                "indexName" : "store_id_1_meta.is_published_1_meta.lowercase_sku_1",
                "isMultiKey" : true,
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 1,
                "direction" : "forward",
                "indexBounds" : {
                    "store_id" : [
                        "[\"my_domain.com\", \"my_domain.com\"]"
                    ],
                    "meta.is_published" : [
                        "[true, true]"
                    ],
                    "meta.lowercase_sku" : [
                        "[MinKey, MaxKey]"
                    ]
                },
                "keysExamined" : 28629,
                "dupsTested" : 28629,
                "dupsDropped" : 103,
                "seenInvalidated" : 0
            }
        }
    },
    "serverInfo" : {
        "version" : "3.2.18",
        "gitVersion" : "4c1bae566c0c00f996a2feb16febf84936ecaf6f"
    },
    "ok" : 1
}

... Все еще сохраняет условие "meta.lowercase_sku" для этапа FETCH, даже если оно прямо в индексе. Есть еще идеи?

1 ответ

Это связано с тем, как планировщик запросов выбирает индекс для использования.

Суть проблемы в том, что у вас есть несколько индексов, которые могут удовлетворить запрос. Допустим, у вас есть два индекса с одинаковым префиксом:

db.test.createIndex({a:1, b:1})

а также

db.test.createIndex({a:1, b:1, c:1})

Теперь, если вы делаете:

db.test.find({a:1, b:1})

Как MongoDB знает, какой индекс может удовлетворить этот запрос? В зависимости от расположения на диске (среди многих вещей) один индекс может быть быстрее другого.

Итак, сначала он будет ранжировать планы по следующей формуле:

score = baseScore + productivity + tieBreakers

где в настоящее время в MongoDB 3.6.3, baseScore = 1, productivity зависит от того, сколько работы потребуется для того, чтобы план дал максимально возможный результат, и tieBreakers зависит от того, нужен ли индекс FETCH, не имея SORT этап или пересечение индекса.

Если два плана дали одинаковые оценки (как и два плана выше), то это приводит их в гонку, чтобы получить 101 результат быстрее. Выигрышный план будет кэширован, и план будет выполнен. Как вы можете себе представить, это может быть недетерминированным, а также тратить время сервера. Лучше всего, если сервер может просто выбрать индекс, основанный на системе оценки.

Лучший способ индексировать коллекцию - это удалить несущественные индексы, чтобы MongoDB мог сразу же выбрать индекс.

Примечание: если вас интересуют мелкие детали, вы можете:

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