Как влияет порядок составных индексов на производительность MongoDB?

Нам нужно создать составной индекс в том же порядке, в котором запрашиваются параметры. Этот заказ имеет значение для производительности вообще?

Представьте, что у нас есть коллекция всех людей на земле с индексом sex (99,9% времени "мужской" или "женский", но, тем не менее, строка (не двоичная)) и индекс на name,

Если бы мы хотели иметь возможность выбрать всех людей определенного sex с определенным nameНапример, все "мужчины" по имени "Джон", лучше иметь составной индекс с sex первый или name первый? Почему бы и нет)?

3 ответа

Redsandro,

Вы должны рассмотреть Index Cardinality а также Selectivity,


1. Индекс мощности

Индекс кардинальности относится к числу возможных значений для поля. Поле sex имеет только два возможных значения. У него очень низкая мощность. Другие поля, такие как names, usernames, phone numbers, emails и т. д. будет иметь более уникальное значение для каждого документа в коллекции, что считается большим количеством элементов.

  • Большая мощность

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

    Если у вас есть индекс на sex и вы ищете мужчин по имени Джон. Вы бы сузили пространство результатов только примерно на 50%, если бы sex первый. И наоборот, если вы проиндексированы name Вы бы сразу сузили набор результатов до минутной доли пользователей по имени Джон, а затем обратились бы к этим документам для проверки пола.

  • Практическое правило

    Попробуйте создать индексы на high-cardinality ключи или положить high-cardinality ключи сначала в составном индексе. Вы можете прочитать больше об этом в разделе по составным индексам в книге:

    MongoDB Полное руководство


2. Селективность

Кроме того, вы хотите использовать индексы выборочно и писать запросы, которые ограничивают количество возможных документов с индексированным полем. Для простоты рассмотрим следующую коллекцию. Если ваш индекс {name:1}, Если вы запустите запрос { name: "John", sex: "male"}, Вам придется сканировать 1 документ. Потому что вы позволили MongoDB быть избирательным.

{_id:ObjectId(),name:"John",sex:"male"}
{_id:ObjectId(),name:"Rich",sex:"male"}
{_id:ObjectId(),name:"Mose",sex:"male"}
{_id:ObjectId(),name:"Sami",sex:"male"}
{_id:ObjectId(),name:"Cari",sex:"female"}
{_id:ObjectId(),name:"Mary",sex:"female"}

Рассмотрим следующую коллекцию. Если ваш индекс {sex:1}, Если вы запустите запрос {sex: "male", name: "John"}, Вам придется сканировать 4 документы.

{_id:ObjectId(),name:"John",sex:"male"}
{_id:ObjectId(),name:"Rich",sex:"male"}
{_id:ObjectId(),name:"Mose",sex:"male"}
{_id:ObjectId(),name:"Sami",sex:"male"}
{_id:ObjectId(),name:"Cari",sex:"female"}
{_id:ObjectId(),name:"Mary",sex:"female"}

Представьте себе возможные различия в большом наборе данных.


Небольшое объяснение составных индексов

Легко сделать неправильное предположение о составных индексах. Согласно Руководству по индексам MongoDB.

MongoDB поддерживает составные индексы, где одна структура индекса содержит ссылки на несколько полей в документах коллекции. Следующая диаграмма иллюстрирует пример составного индекса для двух полей:

При создании составного индекса 1 индекс будет содержать несколько полей. Так что, если мы индексируем коллекцию по {"sex" : 1, "name" : 1}, индекс будет выглядеть примерно так:

["male","Rick"] -> 0x0c965148
["male","John"] -> 0x0c965149
["male","Sean"] -> 0x0cdf7859
["male","Bro"] ->> 0x0cdf7859
...
["female","Kate"] -> 0x0c965134
["female","Katy"] -> 0x0c965126
["female","Naji"] -> 0x0c965183
["female","Joan"] -> 0x0c965191
["female","Sara"] -> 0x0c965103

Если мы индексируем коллекцию по {"name" : 1, "sex" : 1}, индекс будет выглядеть примерно так:

["John","male"] -> 0x0c965148
["John","female"] -> 0x0c965149
["John","male"] -> 0x0cdf7859
["Rick","male"] -> 0x0cdf7859
...
["Kate","female"] -> 0x0c965134
["Katy","female"] -> 0x0c965126
["Naji","female"] -> 0x0c965183
["Joan","female"] -> 0x0c965191
["Sara","female"] -> 0x0c965103

имеющий {name:1} в качестве префикса будет служить вам гораздо лучше при использовании составных индексов. По этой теме можно прочитать гораздо больше, я надеюсь, что это может дать некоторую ясность.

Я собираюсь сказать, что сам провел эксперимент с этим и обнаружил, что, похоже, нет потери производительности при использовании плохо различимого ключа индекса в первую очередь. (Я использую mongodb 3.4 с wiredtiger, который может отличаться от mmap). Я вставил 250 миллионов документов в новую коллекцию под названием items, Каждый документ выглядел так:

{
    field1:"bob",
    field2:i + "",
    field3:i + ""

"field1" всегда был равен "bob", "field2" был равен iтак что это было совершенно уникально. Сначала я выполнил поиск по field2, и на сканирование 250 миллионов документов ушло более минуты. Затем я создал индекс примерно так:

`db.items.createIndex({field1:1,field2:1})`

Конечно, field1 - это "bob" в каждом отдельном документе, поэтому в индексе необходимо найти несколько элементов, прежде чем найти нужный документ. Однако это был не тот результат, который я получил.

Я сделал еще один поиск по коллекции после создания индекса. На этот раз я получил результаты, которые я перечислил ниже. Вы увидите, что "totalKeysExamined" 1 каждый раз. Так что, возможно, с проводным тигром или что-то еще, они выяснили, как сделать это лучше. Я читал, что wiredtiger фактически сжимает префиксы индекса, так что это может иметь какое-то отношение к нему.

db.items.find({field1:"bob",field2:"250888000"}).explain("executionStats")

{
    "executionSuccess" : true,
    "nReturned" : 1,
    "executionTimeMillis" : 4,
    "totalKeysExamined" : 1,
    "totalDocsExamined" : 1,
    "executionStages" : {
        "stage" : "FETCH",
        "nReturned" : 1,
        "executionTimeMillisEstimate" : 0,
        "works" : 2,
        "advanced" : 1,
        ...
        "docsExamined" : 1,
        "inputStage" : {
            "stage" : "IXSCAN",
            "nReturned" : 1,
            "executionTimeMillisEstimate" : 0,
            ...
            "indexName" : "field1_1_field2_1",
            "isMultiKey" : false,
            ...
            "indexBounds" : {
                "field1" : [
                    "[\"bob\", \"bob\"]"
                ],
                "field2" : [
                    "[\"250888000\", \"250888000\"]"
                ]
            },
            "keysExamined" : 1,
            "seeks" : 1
        }
    }

Затем я создал индекс на field3 (который имеет то же значение, что и поле 2). Затем я искал:

db.items.find ({field3: "250888000"});

Это заняло те же 4 мсек, что и с составным индексом. Я повторил это несколько раз с разными значениями для field2 и field3 и каждый раз получал незначительные различия. Это говорит о том, что с помощью wiredtiger не существует ухудшения производительности за плохую дифференциацию в первом поле индекса.

Обратите внимание, что несколько предикатов равенства не нужно упорядочивать от наиболее избирательного к наименее избирательному. Это руководство было предоставлено в прошлом, однако оно ошибочно из-за природы индексов B-Tree и того, как на листовых страницах B-Tree будет хранить комбинации всех значений полей. Таким образом, существует точно такое же количество комбинаций, независимо от порядка нажатия клавиш.

https://www.alexbevi.com/blog/2020/05/16/optimizing-mongodb-compound-indexes-the-equality-sort-range-esr-rule/

Эта статья в блоге не согласна с принятым ответом. Тест в другом ответе также показывает, что это не имеет значения. Автор этой статьи - «старший инженер по техническим услугам в MongoDB», что звучит для меня как уважаемый человек в этой теме, поэтому я полагаю, что порядок действительно не влияет на производительность в полях равенства. Вместо этого я буду следовать правилу ESR.

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