Полнотекстовые индексы
Полнотекстовые индексы — это специализированный тип вторичного индекса, который позволяет эффективно выполнять поиск по текстовому содержимому в колонках таблицы: по словам, фразам и (с N-граммами) по подстрокам. В отличие от традиционных вторичных индексов, оптимизированных для поиска по равенству или диапазону, полнотекстовые индексы обеспечивают поиск по текстовому содержимому.
Общее описание полнотекстового поиска см. в разделе Полнотекстовый поиск.
Характеристики полнотекстовых индексов
Полнотекстовые индексы в YDB строятся путём токенизации текста и создания инвертированного индекса. Это позволяет:
- быстро фильтровать строки через FulltextMatch;
- ранжировать результаты по релевантности (BM25) через FulltextScore при использовании fulltext_relevance;
- применять нормализацию регистра, стемминг и N-граммы с помощью фильтров индекса.
В текущей реализации доступны два варианта индекса:
- fulltext_plain — базовый полнотекстовый индекс;
- fulltext_relevance — полнотекстовый индекс со статистикой BM25 для расчёта релевантности.
Кроме этого, полнотекстовый индекс может быть покрывающим (через COVER) и включать копию данных дополнительных колонок из основной таблицы.
Виды полнотекстовых индексов
YDB поддерживает два типа полнотекстовых индексов, которые различаются составом хранимой статистики:
- fulltext_plain — хранит только инвертированный индекс. Поддерживает фильтрацию через FulltextMatch, но не позволяет ранжировать результаты по релевантности.
- fulltext_relevance — дополнительно хранит частотную статистику (TF-IDF / BM25), необходимую для работы FulltextScore.
Индекс любого из этих типов также может быть с фильтрацией для ограничения поиска конкретным логическим разделом.
Базовый полнотекстовый индекс (fulltext_plain)
Используйте fulltext_plain, когда достаточно проверить наличие термов в тексте без ранжирования по релевантности. Такой индекс компактнее fulltext_relevance и подходит для большинства задач фильтрации.
Пример создания глобального полнотекстового индекса по колонке body:
ALTER TABLE articles
ADD INDEX ft_index
GLOBAL USING fulltext_plain
ON (body)
WITH (tokenizer=standard, use_filter_lowercase=true);
Здесь tokenizer=standard разбивает текст на слова по пробелам и знакам препинания, а use_filter_lowercase=true нормализует все токены к нижнему регистру — это делает поиск регистронезависимым.
Пример запроса к индексу:
SELECT id, title
FROM articles VIEW ft_index
WHERE FulltextMatch(body, "поисковые термы")
LIMIT 20;
Полнотекстовый индекс для ранжирования (fulltext_relevance)
fulltext_relevance хранит инвертированный индекс вместе с частотной статистикой (BM25), которая позволяет функции FulltextScore вычислять оценку релевантности документа запросу. Используйте этот тип, когда нужно не просто найти документы с нужными словами, но и упорядочить их по степени соответствия.
Пример создания индекса:
ALTER TABLE articles
ADD INDEX ft_index
GLOBAL USING fulltext_relevance
ON (body)
WITH (tokenizer=standard, use_filter_lowercase=true);
Пример запроса с ранжированием:
SELECT id, title, FulltextScore(body, "поисковые термы") AS relevance
FROM articles VIEW ft_index
WHERE FulltextScore(body, "поисковые термы") > 0
ORDER BY relevance DESC
LIMIT 10;
Поиск по подстроке (N-граммы)
Если вам нужен поиск по подстроке, создайте индекс с N-граммами. Доступны два типа N-грамм:
- Обычные N-граммы (
use_filter_ngram) — разбивают слова на все возможные подстроки заданной длины, что позволяет находить совпадения в любой части слова. Например, слово "search" будет разбито на "sea", "ear", "arc", "rch" и т.д. - Краевые N-граммы (
use_filter_edge_ngram) — создают подстроки только от начала слова, что идеально подходит для автодополнения. Например, слово "search" будет разбито на "se", "sea", "sear", "searc", "search".
При использовании N-грамм станут доступны:
- FulltextMatch(..., "Wildcard" AS Mode) — поиск с шаблонами
%и_(аналогичноLIKE); - предикаты
LIKE/ILIKEпо индексируемой текстовой колонке — YDB автоматически использует N-граммовый индекс при обращении к нему черезVIEW IndexName.
Пример индекса с N-граммами:
ALTER TABLE articles
ADD INDEX ngram_index
GLOBAL USING fulltext_plain
ON (body)
WITH (
tokenizer=standard,
use_filter_lowercase=true,
use_filter_ngram=true,
filter_ngram_min_length=3,
filter_ngram_max_length=5
);
Пример запроса с FulltextMatch:
SELECT id, title
FROM articles VIEW ngram_index
WHERE FulltextMatch(body, "%обуч%", "Wildcard" AS Mode)
LIMIT 20;
Пример запроса с LIKE:
SELECT id, title
FROM articles VIEW ngram_index
WHERE body LIKE "%обуч%ние%"
LIMIT 20;
Запрос с LIKE / ILIKE использует ту же логику, что и FulltextMatch(body, ..., "Wildcard" AS Mode), и обращается к тому же N-граммовому индексу.
Полнотекстовый индекс с фильтрацией
Полнотекстовый индекс с фильтрацией позволяет выполнять полнотекстовый поиск в рамках логического раздела, заданного значениями колонок фильтрации. Чтобы создать такой индекс, укажите одну или несколько колонок фильтрации перед текстовой колонкой в секции ON. Последней колонкой должна быть текстовая, остальные могут быть любого сравнимого типа:
ALTER TABLE articles
ADD INDEX ft_index
GLOBAL USING fulltext_plain
ON (user_id, body)
WITH (tokenizer=standard, use_filter_lowercase=true);
Запросы к такому индексу должны включать предикат равенства для каждой колонки фильтрации:
SELECT id, title
FROM articles VIEW ft_index
WHERE user_id = 42 AND FulltextMatch(body, "поисковые термы")
LIMIT 20;
Поддерживается несколько колонок фильтрации. Предикаты равенства в WHERE могут быть указаны в любом порядке — YDB автоматически упорядочит их в соответствии с порядком колонок в индексе.
Типы первичного ключа
Внутри инвертированного индекса каждый проиндексированный документ идентифицируется числовым идентификатором документа (doc_id). Способ получения doc_id зависит от первичного ключа основной таблицы:
- Первичный ключ из одной целочисленной колонки (
Uint64,Int64,Uint32илиInt32) — значение первичного ключа используется напрямую в качествеdoc_id. Это самая компактная форма, дополнительные структуры не создаются. - Любой другой первичный ключ (например,
Utf8,String, другие нецелочисленные типы или составной ключ из нескольких колонок) — YDB поддерживает системную колонку__ydb_row_idтипаUint64и использует её в качествеdoc_id.
При создании полнотекстового индекса на таблице, первичный ключ которой не является одной целочисленной колонкой, YDB автоматически:
- добавляет в таблицу колонку
__ydb_row_id— существующие строки заполняются во время построения индекса; - генерирует значение
__ydb_row_idдля каждой строки, в которой эта колонка не указана вINSERT/UPSERT; - создаёт уникальный вторичный индекс с именем
__ydb_unique_row_idпо колонке__ydb_row_id. Во время запроса этот индекс сопоставляет найденный__ydb_row_idс первичным ключом таблицы перед чтением строки из основной таблицы.
Если на таблице создаётся несколько полнотекстовых индексов, все они переиспользуют одну и ту же колонку __ydb_row_id и индекс __ydb_unique_row_id — эти структуры создаются для таблицы только один раз.
Важно
Колонкой __ydb_row_id и индексом __ydb_unique_row_id управляет YDB:
- Позвольте YDB заполнять
__ydb_row_id— не указывайте её вINSERT/UPSERT, и значение проставится автоматически. Модификация колонки__ydb_row_idпользователем запрещена — любые попытки задать или изменить значение этой колонки будут отклонены. - Индекс
__ydb_unique_row_idнельзя удалить, пока от него зависит хотя бы один полнотекстовый индекс. Сначала удалите зависимые полнотекстовые индексы.
Полный синтаксис полнотекстовых индексов
Создание полнотекстового индекса:
- при создании таблицы: CREATE TABLE;
- добавление к существующей таблице: ALTER TABLE.
Полный синтаксис запроса к полнотекстовому индексу:
Функции и выражения для полнотекстового поиска:
Примечание
Полнотекстовый индекс не будет автоматически выбран оптимизатором, поэтому его нужно указывать явно с помощью VIEW IndexName.
Если не использовать выражение VIEW, запросы с FulltextMatch / FulltextScore завершатся с ошибкой.
Это ограничение может быть снято в будущих версиях YDB.
Обновление полнотекстовых индексов
Полнотекстовые индексы автоматически поддерживаются при модификации данных. Таблицы с полнотекстовыми индексами поддерживают:
INSERTUPSERTREPLACEUPDATEDELETE
Удаление полнотекстовых индексов
ALTER TABLE articles DROP INDEX ft_index;
Ограничения
- Для таблиц с нецелочисленным или составным первичным ключом автоматически создаются системная колонка
__ydb_row_idи уникальный индекс__ydb_unique_row_id(см. Типы первичного ключа). BulkUpsertне поддерживается для таблиц с полнотекстовыми индексами.- Использование полнотекстового индекса необходимо задавать явно с помощью
VIEW IndexName. - В одном полнотекстовом индексе индексируется одна текстовая колонка.
FulltextMatch/FulltextScoreнельзя использовать cORилиNOT. Допускается комбинация с другими предикатами черезAND.- В одном чтении через
VIEWподдерживается только один полнотекстовый предикат: нельзя использовать несколькоFulltextScoreи нельзя смешиватьFulltextMatchиFulltextScoreв одномWHERE. - Для доступа к индексу по релевантности требуется ограничение
FulltextScore(...) > 0вWHERE(иначе запрос завершится с ошибкой). - Полнотекстовые индексы с фильтрацией: для каждой колонки фильтрации требуется предикат равенства в
WHERE. - Полнотекстовые индексы с фильтрацией: колонки фильтрации не должны быть колонками первичного ключа.