Полнотекстовые индексы
Полнотекстовые индексы — это специализированный тип вторичного индекса, который позволяет эффективно выполнять поиск по текстовому содержимому в колонках таблицы: по словам, фразам и (с 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-граммовому индексу.
Полный синтаксис полнотекстовых индексов
Создание полнотекстового индекса:
- при создании таблицы: CREATE TABLE;
- добавление к существующей таблице: ALTER TABLE.
Полный синтаксис запроса к полнотекстовому индексу:
Функции и выражения для полнотекстового поиска:
Примечание
Полнотекстовый индекс не будет автоматически выбран оптимизатором, поэтому его нужно указывать явно с помощью VIEW IndexName.
Если не использовать выражение VIEW, запросы с FulltextMatch / FulltextScore завершатся с ошибкой.
Это ограничение может быть снято в будущих версиях YDB.
Обновление полнотекстовых индексов
Полнотекстовые индексы автоматически поддерживаются при модификации данных. Таблицы с полнотекстовыми индексами поддерживают:
INSERTUPSERTREPLACEUPDATEDELETE
Удаление полнотекстовых индексов
ALTER TABLE articles DROP INDEX ft_index;
Ограничения
- Для таблиц с полнотекстовыми индексами поддерживается только первичный ключ из одной колонки типа
Uint64. BulkUpsertне поддерживается для таблиц с полнотекстовыми индексами.- Использование полнотекстового индекса необходимо задавать явно с помощью
VIEW IndexName. - В одном полнотекстовом индексе индексируется одна текстовая колонка.
FulltextMatch/FulltextScoreнельзя использовать cORилиNOT. Допускается комбинация с другими предикатами черезAND.- В одном чтении через
VIEWподдерживается только один полнотекстовый предикат: нельзя использовать несколькоFulltextScoreи нельзя смешиватьFulltextMatchиFulltextScoreв одномWHERE. - Для доступа к индексу по релевантности требуется ограничение
FulltextScore(...) > 0вWHERE(иначе запрос завершится с ошибкой).