В прошлом уроке мы закончили кластер про агрегацию и выяснили, что быстрый конвейер начинается с индекса. Сегодня открываем кластер про индексы и производительность. В вводном уроке про индексы мы строили индекс по одному полю, составной и уникальный, и споткнулись об уникальный 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 ₽.
Оплата пока не подключена: премиум и курсы открываются промокодом.