Полнотекстовый поиск в PostgreSQL: какой вариант выбрать
Для поиска по словам, опечаткам и произвольным фрагментам текста PostgreSQL предлагает разные инструменты — и они не взаимозаменяемы. Сравниваем `tsvector`, `pg_trgm` и `ILIKE`, чтобы выбрать подход без лишней нагрузки и неожиданностей.

Если пользователю нужно найти статью по словам из заголовка и текста, обычный `ILIKE '%запрос%'` кажется самым быстрым решением. Но он не понимает словоформы и на большой таблице может читать слишком много строк. В PostgreSQL 16 есть три практичных варианта: полнотекстовый поиск через `tsvector`, поиск по триграммам через `pg_trgm` и простой `ILIKE`. Выбор зависит от того, что именно человек вводит и какие совпадения ожидает.
Сначала определите, что значит «найти»
Запрос «доставка задерживается» может означать поиск двух слов в документе, совпадение точной фразы или поиск текста, похожего на введённый с опечаткой. Для базы это разные операции. Полнотекстовый поиск разбирает слова и применяет словарь языка; триграммы ищут похожие фрагменты; `ILIKE` проверяет буквальное совпадение с шаблоном.
В примерах ниже — PostgreSQL 16 и таблица `articles` с полями `title` и `body`. Версия важна: синтаксис генерируемых столбцов и доступность отдельных функций менялись в прошлых выпусках PostgreSQL. Если у вас другая версия, перед переносом DDL сверьтесь с документацией именно для неё.
Три варианта и цена каждого
| Подход | Лучше всего подходит для | Плюсы | Цена и ограничения |
|---|---|---|---|
| `tsvector` и GIN | Поиска по словам, словоформам, фразам; ранжирования результатов | Понимает особенности языка, умеет отбирать документы по запросу и оценивать релевантность | Нужно настроить текстовую конфигурацию и поддерживать поисковый вектор; опечатки сам по себе не исправляет |
| `pg_trgm` и GIN | Поиска подстрок и похожих строк, в том числе с опечатками | Поддерживает шаблоны `LIKE` и `ILIKE`, а также поиск по сходству | Триграммы требуют места; короткие строки и запросы могут плохо использовать индекс |
| `ILIKE` без индекса | Небольших таблиц, редких фильтров и быстрого прототипа | Простая буквальная проверка без расширений и отдельной поисковой колонки | На больших объёмах может потребовать последовательного чтения таблицы; не знает словоформ и смысла слов |
Когда нужен поиск по словам: `tsvector`
Для русского текста это обычно отправная точка. PostgreSQL разбивает документ на лексемы и использует конфигурацию `russian`, чтобы учитывать словоформы. Поэтому запрос по одной форме слова может найти документ с другой. Это не семантический поиск: синонимы и смысл фразы без совпадающих лексем автоматически не появятся.
В PostgreSQL 16 поисковый вектор удобно хранить в генерируемом столбце. Значение обновляется вместе с исходными полями, а GIN-индекс ускоряет отбор кандидатов.
CREATE TABLE articles (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
title text NOT NULL,
body text NOT NULL,
search_vector tsvector GENERATED ALWAYS AS (
to_tsvector('russian', coalesce(title, '') || ' ' || coalesce(body, ''))
) STORED
);
CREATE INDEX articles_search_vector_gin
ON articles USING GIN (search_vector);
Ищите через `websearch_to_tsquery`: эта функция принимает привычный поисковый ввод, включая кавычки для фразы и логическое `OR`. Язык в запросе должен совпадать с языком при построении вектора.
SELECT id, title,
ts_rank_cd(search_vector, websearch_to_tsquery('russian', $1)) AS rank
FROM articles
WHERE search_vector @@ websearch_to_tsquery('russian', $1)
ORDER BY rank DESC
LIMIT 20;
Параметр `$1` передавайте как параметр подготовленного запроса, а не склеивайте с SQL строкой. Рейтинг помогает упорядочить уже найденные документы, но не гарантирует, что первые результаты будут «правильными» для вашей предметной области. Если важнее заголовок, можно формировать вектор с весами полей и проверить результат на реальных запросах; это отдельная настройка ранжирования.
Выбирайте не самый умный на вид индекс, а совпадение с ожиданием пользователя: словоформы, опечатки и буквальная подстрока — три разных требования.
Когда важны подстроки и опечатки: `pg_trgm`
Расширение `pg_trgm` разбивает строки на последовательности из трёх символов. Это помогает ускорить поиск подстроки, например по части артикула или названия, и найти похожие строки. Расширение нужно включить в каждой базе, где вы собираетесь его использовать:
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX articles_title_trgm_gin
ON articles USING GIN (title gin_trgm_ops);
После этого запрос с `ILIKE` может использовать индекс:
SELECT id, title
FROM articles
WHERE title ILIKE '%' || $1 || '%'
LIMIT 20;
Для нечёткого совпадения есть оператор сходства `%`. Он сравнивает строку с порогом сходства, заданным в настройках `pg_trgm`; результат зависит от этого порога. Не включайте его в пользовательский поиск, не проверив порог на ваших данных: слишком высокий пропустит полезные результаты, слишком низкий добавит шум. Триграммы не заменяют языковой словарь: поиск по словоформам и разбор логики запроса — задача `tsvector`.
Когда оставить `ILIKE` без специального индекса
Если таблица мала, запрос выполняется редко, а совпадение должно быть буквальным, отдельное расширение может не окупиться. Например, внутренний экран для сотни записей не обязательно усложнять дополнительными индексами. Но шаблон `'%слово%'` обычно нельзя эффективно обслужить обычным B-tree индексом: измерьте план запроса, прежде чем рассчитывать на ускорение.
Обычный `ILIKE` не приводит слова к начальной форме. Запрос «книги» не обязан найти «книга». Он также не исправит опечатку. Если эти сценарии важны, добавление индекса B-tree к тому же условию проблему не решит.
Как выбрать на проекте
- Нужны слова, словоформы и релевантность. Начните с `tsvector` и GIN. Проверьте, как конфигурация `russian` обрабатывает термины из вашей предметной области.
- Нужны подстроки или похожие названия. Подключите `pg_trgm` и индексируйте конкретное поле, по которому идёт поиск. Не индексируйте все текстовые колонки «на всякий случай».
- Нужен простой поиск по небольшой таблице. Оставьте `ILIKE`, пока измерения не покажут, что это узкое место.
- Пользователи ожидают и словоформы, и опечатки. Сочетайте полнотекстовый отбор с отдельным триграммным сценарием. Сначала определите, как объединять и ранжировать результаты: два независимых списка сами по себе не дают единой релевантности.
Сравнивайте варианты на одинаковых запросах и данных. Зафиксируйте размер таблицы, число одновременных запросов и характер текста; проверьте планы через `EXPLAIN (ANALYZE, BUFFERS)` на тестовой копии. Смотрите не только на время ответа, но и на долю полезных результатов, размер индексов и задержку записи: GIN-индексы занимают место и добавляют работу при изменении данных. Универсальных цифр ускорения здесь нет — они зависят от распределения текста и нагрузки.
Что ломается и где подход не подходит
- Индекс создали, но запрос всё равно медленный. Проверьте план выполнения, соответствие выражения индексируемому столбцу и фактическую селективность запроса. Само наличие индекса не означает, что планировщик выберет его.
- Поиск по короткому фрагменту ничего не ускорил. У короткого шаблона может не быть полезных триграмм для отбора, и индекс не даст ожидаемого выигрыша. Измерьте запрос на реальных значениях.
- Русский текст ищется не так, как ожидают пользователи. Проверьте конфигурацию, стоп-слова и разбор терминов. Для названий, кодов и артикулов языковая нормализация может оказаться нежелательной.
- Результаты по опечаткам стали слишком шумными. Проверьте порог сходства и ограничьте поиск нужными полями или дополнительными условиями. Не считайте любое похожее слово релевантным.
Термин. Лексема — нормализованная форма слова, которую PostgreSQL использует в полнотекстовом векторе. Она помогает сопоставлять словоформы, но не означает, что база понимает значение текста.
Что делать дальше
Возьмите 20–50 реальных поисковых запросов и отметьте, какие результаты должны находиться: по словоформе, фразе, фрагменту или опечатке. На этих примерах сравните `tsvector` и `pg_trgm`, проверьте планы запросов и измерьте размер индексов. Если поиск по-прежнему требует синонимов, исправления запросов или объединения результатов из разных источников, зафиксируйте это как отдельное требование, а не пытайтесь решить его одним индексом.
Поиск источников: Реальные статьи и книги со ссылками, библиография по ГОСТ
Спросить за 15 ₽Источники: PostgreSQL 16: полнотекстовый поиск, PostgreSQL 16: расширение pg_trgm
Частые вопросы
Что выбрать для поиска по русским словам и словоформам?
Найдёт ли полнотекстовый поиск слова с опечаткой?
Можно ли использовать `ILIKE` для поиска по большой таблице?
Нужно ли вручную обновлять `tsvector` после изменения текста?
Можно ли совместить полнотекстовый поиск и триграммы?
Почему PostgreSQL не использует созданный индекс?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.