Раздел 36 · MongoDB с нуля

Типы индексов: составные, multikey, текстовые, геопространственные

junior~25 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

В прошлом уроке мы закончили кластер про агрегацию и выяснили, что быстрый конвейер начинается с индекса. Сегодня открываем кластер про индексы и производительность. В вводном уроке про индексы мы строили индекс по одному полю, составной и уникальный, и споткнулись об уникальный email, которого нет ни у кого. Теперь посмотрим, какие ещё индексы есть у MongoDB, и заодно починим email.

Указатели в путеводителе

Открой толстый путеводитель по городу. В конце у него не один указатель, а несколько. Алфавитный список музеев. Список по темам, где один музей стоит сразу под «историей» и «архитектурой». Указатель слов, по которому находишь страницу, где упоминается «фонтан», в какой бы форме он ни стоял. Карта с сеткой, по которой видно, что рядом с тобой. И вкладыш «временные выставки», который в конце сезона выбрасывают.

У MongoDB индексы устроены так же: под каждый вид вопроса свой указатель. Разберём их по очереди на учебной базе магазина. Все примеры сняты с настоящего MongoDB 8.

Составной: напоминание

Составной индекс мы уже строили: { 'author.name': 1, year: -1 } упорядочивает книги по автору, а у одного автора по году. Он помогает запросам, которые фильтруют по первому полю и дальше по следующим. Порядок полей в составном индексе решает почти всё, и этому посвящён весь следующий урок. Здесь двигаемся дальше.

Multikey: индекс по массиву

У книг есть массив меток tags. Индекс по нему строится той же командой, что и по обычному полю:

db.books.createIndex({ tags: 1 })
db.books.find({ tags: 'сатира' }, { title: 1 }).sort({ _id: 1 })
tags_1
[
  { _id: 2, title: 'Собачье сердце' },
  { _id: 6, title: 'Мёртвые души' }
]

Внутри индекс другой. Если поле хранит массив, MongoDB кладёт в индекс отдельную запись на каждый элемент: у «Мастера и Маргариты» три метки, значит, три записи. Такой индекс называют multikey. Он похож на список по темам в путеводителе, где один музей стоит под несколькими рубриками. Отдельно включать его не нужно: MongoDB сама замечает массив. В плане explain это видно по флагу:

db.books.find({ tags: 'сатира' }).explain('executionStats').queryPlanner.winningPlan.inputStage.isMultiKey
true

У multikey одно ограничение. В составном индексе массивом может быть только одно поле. Попробуем индекс по меткам и по авторам отзывов, а reviews тоже массив:

db.books.createIndex({ tags: 1, 'reviews.user': 1 })
Uncaught MongoServerError[CannotIndexParallelArrays]: Index build failed: ... :: caused by :: cannot index parallel arrays [reviews] [tags]

Служебные подробности в середине заменены многоточием. Два массива в одном индексе дали бы произведение всех пар элементов, и сервер такой индекс не строит. Это аналог индекса GIN по массиву из урока про индексы в PostgreSQL, только в MongoDB он строится обычной командой.

Текстовый: поиск по словам

Покупатель вводит в поиск «сердца». Регулярное выражение по title найдёт только точное сочетание букв, а в названии стоит «сердце». Для поиска по словам есть текстовый индекс. Он работает как указатель слов в путеводителе: разбивает текст на слова и хранит их основы.

Построим один индекс сразу по названиям и текстам отзывов. Язык важен: по нему MongoDB решает, как отрезать окончания.

db.books.createIndex(
  { title: 'text', 'reviews.text': 'text' },
  { default_language: 'russian' }
)
title_text_reviews.text_text

Ищут по нему оператором $text:

db.books.find({ $text: { $search: 'сердца' } }, { title: 1 })
db.books.find({ $text: { $search: 'понятный' } }, { title: 1 })
[ { _id: 2, title: 'Собачье сердце' } ]
[ { _id: 7, title: 'Грокаем алгоритмы' } ]

«Сердца» нашло «Собачье сердце»: у обоих слов общая основа. «Понятный» нашло «Грокаем алгоритмы», хотя в книге нет такого слова: в отзыве Павла написано «Понятно даже новичку». Фразу целиком ищут в двойных кавычках, '"мёртвые души"', а насколько документ подходит, показывает служебное значение { $meta: 'textScore' }.

Два правила текстового индекса:

  • Он один на коллекцию. Второй текстовый индекс сервер не создаст, нужные поля перечисляют в одном.
  • Он грубее настоящего поиска. Нет нечёткого поиска по опечаткам, нет подсказок. Для поиска уровня интернет-магазина берут Atlas Search или отдельный движок вроде Elasticsearch.

Похожий механизм в PostgreSQL называется полнотекстовым поиском, его мы разбирали в уроке про tsvector.

урок из курса

Урок из курса «MongoDB с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.

Оплата пока не подключена: премиум и курсы открываются промокодом.