MySQL Full-Text Search в 2026 году: возможности, ограничения и сравнение с Elasticsearch
Введение: когда встроенного поиска достаточно, а когда нет
В 2026 году вопрос «использовать ли MySQL Full-Text Search или подключать Elasticsearch» остаётся актуальным для сотен тысяч проектов. Многие команды тянут к себе Elasticsearch по инерции, не проверив, справится ли нативный поиск MySQL с реальной нагрузкой. Другие, напротив, упираются в потолок FULLTEXT-индексов, когда каталог разрастается до миллионов записей.
Простая эвристика: если у вас до 5–10 млн строк, поисковые запросы не сложнее булевых условий, а требования к релевантности умеренные — MySQL Full-Text Search закроет задачу без дополнительной инфраструктуры. Как только появляются требования к фасетному поиску, синонимам, мультиязычному стеммингу или потоковой индексации — пора смотреть в сторону Elasticsearch.
Типы Full-Text индексов и режимы поиска в MySQL
FULLTEXT-индекс
MySQL поддерживает FULLTEXT-индексы только для таблиц InnoDB и MyISAM. Создать индекс можно при создании таблицы или позже:
-- При создании таблицы
CREATE TABLE articles (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
body TEXT NOT NULL,
FULLTEXT idx_fts (title, body)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Добавление к существующей таблице
ALTER TABLE articles ADD FULLTEXT INDEX idx_fts (title, body);
-- Или через CREATE INDEX
CREATE FULLTEXT INDEX idx_fts ON articles (title, body);
MATCH...AGAINST и режимы поиска
Оператор MATCH(col1, col2) AGAINST('query' IN MODE) — единственный способ задействовать FULLTEXT-индекс. MySQL поддерживает три режима:
- IN NATURAL LANGUAGE MODE — режим по умолчанию. MySQL разбивает запрос на токены, исключает стоп-слова и ранжирует результаты по TF-IDF-подобному алгоритму.
- IN BOOLEAN MODE — булев поиск с операторами
+,-,*,"",>,<. Позволяет строить сложные запросы без учёта релевантности по умолчанию. - WITH QUERY EXPANSION — двухпроходный поиск: сначала находит релевантные документы, затем расширяет запрос словами из этих документов. Повышает полноту, но может снизить точность.
-- NATURAL LANGUAGE MODE
SELECT id, title,
MATCH(title, body) AGAINST('полнотекстовый поиск MySQL') AS score
FROM articles
WHERE MATCH(title, body) AGAINST('полнотекстовый поиск MySQL')
ORDER BY score DESC
LIMIT 20;
-- BOOLEAN MODE: обязательное слово, исключение, фраза
SELECT id, title
FROM articles
WHERE MATCH(title, body) AGAINST('+MySQL -Oracle "полнотекстовый поиск"' IN BOOLEAN MODE);
-- WITH QUERY EXPANSION
SELECT id, title
FROM articles
WHERE MATCH(title, body) AGAINST('индексация' WITH QUERY EXPANSION)
LIMIT 10;
Настройка и оптимизация FTS-индексов
Минимальная длина слова
По умолчанию MySQL индексирует слова длиной от 3 символов (innodb_ft_min_token_size = 3 для InnoDB, ft_min_word_len = 4 для MyISAM). Для русскоязычного контента это нередко проблема: короткие значимые слова («год», «дом», «SQL») выпадают из индекса.
# my.cnf / my.ini
[mysqld]
# Для InnoDB
innodb_ft_min_token_size = 2
# Для MyISAM (если используется)
ft_min_word_len = 2
# Отключаем встроенные стоп-слова (или указываем свой файл)
innodb_ft_enable_stopword = OFF
# innodb_ft_server_stopword_table = 'mydb/my_stopwords'
После изменения параметров необходимо пересоздать индекс: выполнить OPTIMIZE TABLE articles; или пересоздать FULLTEXT-индекс.
Ngram-парсер для кириллицы и CJK
Стандартный встроенный парсер плохо работает с русским языком: он не умеет в морфологию и опирается на пробелы как разделители. Для поиска по подстрокам и поддержки кириллицы рекомендуется ngram-парсер, доступный с MySQL 5.7.
# my.cnf
[mysqld]
ngram_token_size = 2
-- Создание индекса с ngram-парсером
CREATE TABLE products (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(512) NOT NULL,
description TEXT,
FULLTEXT idx_ngram (name, description) WITH PARSER ngram
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Поиск по подстроке
SELECT id, name
FROM products
WHERE MATCH(name, description) AGAINST('смартф' IN BOOLEAN MODE);
Ngram-индекс разбивает текст на все возможные N-граммы заданного размера. При ngram_token_size = 2 слово «Москва» даст биграммы: «мо», «ос», «ск», «кв», «ва». Это позволяет искать по любой подстроке, но увеличивает размер индекса в 3–5 раз по сравнению со стандартным парсером.
Стоп-слова
MySQL поставляется со списком англоязычных стоп-слов. Для русскоязычных проектов нужно либо отключить стандартный список, либо подключить собственный:
-- Создаём таблицу стоп-слов
CREATE TABLE mydb.stopwords (
value VARCHAR(30) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO mydb.stopwords VALUES
('и'), ('в'), ('на'), ('с'), ('по'), ('для'), ('из'), ('от');
-- В my.cnf указываем путь к таблице:
-- innodb_ft_server_stopword_table = 'mydb/stopwords'
Практические SQL-примеры для сложных сценариев поиска
Поиск с фильтрацией и сортировкой по релевантности
SELECT
p.id,
p.name,
p.price,
MATCH(p.name, p.description) AGAINST(:query IN BOOLEAN MODE) AS relevance
FROM products p
WHERE
p.category_id = :category_id
AND p.active = 1
AND MATCH(p.name, p.description) AGAINST(:query IN BOOLEAN MODE)
ORDER BY relevance DESC, p.created_at DESC
LIMIT :limit OFFSET :offset;
Бустинг по полю: заголовок важнее описания
SELECT
id,
title,
(
MATCH(title) AGAINST(:query IN BOOLEAN MODE) * 3 +
MATCH(body) AGAINST(:query IN BOOLEAN MODE)
) AS boosted_score
FROM articles
HAVING boosted_score > 0
ORDER BY boosted_score DESC
LIMIT 20;
Обратите внимание: для этого трюка нужны два отдельных FULLTEXT-индекса — один по title, другой по body.
Поиск с учётом опечаток через SOUNDEX (обходной путь)
-- Грубое приближение: FTS + SOUNDEX-фильтр
SELECT id, title
FROM articles
WHERE MATCH(title, body) AGAINST(:query IN BOOLEAN MODE)
OR SOUNDEX(title) = SOUNDEX(:query)
LIMIT 20;
SOUNDEX в MySQL ориентирован на английский язык и плохо работает с кириллицей. Для нечёткого поиска на русском лучше рассматривать Elasticsearch или предобработку на стороне PHP/Laravel.
Релевантность и ранжирование: как MySQL считает score
В режиме IN NATURAL LANGUAGE MODE MySQL вычисляет score по формуле, близкой к TF-IDF: чем чаще слово встречается в документе (TF) и чем реже — во всей коллекции (IDF), тем выше вес. Документы, в которых искомое слово встречается в более чем 50% строк, получают нулевой score и не возвращаются — это типичная ловушка при небольших наборах данных (менее 20 строк).
Чтобы повысить качество ранжирования в MySQL, применяют несколько техник:
- Разделять индексы по полям с разным весом (title, tags, body) и суммировать score с коэффициентами.
- Хранить предвычисленный «буст» документа (рейтинг, число просмотров) и перемножать на FTS-score.
- Использовать
IN BOOLEAN MODEдля явного управления весами операторами>и<.
-- Явный буст через оператор > в BOOLEAN MODE
SELECT id, title
FROM articles
WHERE MATCH(title, body)
AGAINST('(>MySQL <база данных) +поиск' IN BOOLEAN MODE);
Ограничения MySQL FTS: честный разбор
Масштабирование
Полнотекстовый индекс в InnoDB хранится в отдельных вспомогательных таблицах. При активной записи обновление индекса происходит через буфер (FTS_DOC_ID, DELETED-список), что при высоком DML-потоке приводит к росту фонового merge-потока и просадкам производительности. На таблицах от 50 млн строк индексация заметно замедляется.
Релевантность
TF-IDF без морфологии, без синонимов, без учёта позиции слова в документе — это весомое ограничение. Для e-commerce-поиска с опечатками, транслитерацией и синонимами MySQL FTS проигрывает Elasticsearch принципиально.
Поддержка языков
Без сторонних парсеров MySQL не умеет в стемминг: «бегу», «бежал», «бег» — разные токены. Ngram-парсер решает проблему частично, через грубый поиск по подстрокам, но не является полноценной заменой морфологического анализатора.
Другие ограничения
- Нет фасетного поиска (aggregations).
- Нет поиска по вложенным структурам и JSON без дополнительных усилий.
- FULLTEXT-индекс не поддерживается для столбцов с типом
JSONнапрямую. - Нет встроенной поддержки синонимов.
- Минимальная длина ngram-токена ограничена снизу (обычно 1–2 символа), что влияет на размер индекса.
Сравнение с Elasticsearch: когда переходить, а когда оставаться
Ниже — честный анализ по ключевым критериям.
Когда MySQL FTS достаточно
- База данных до 5–10 млн документов, умеренная нагрузка на чтение.
- Простые сценарии: поиск по статьям блога, по SKU, по имени пользователя.
- Нет требований к фасетам, автодополнению и нечёткому поиску.
- Команда без опыта эксплуатации Elasticsearch-кластера.
- Бюджет не позволяет поддерживать отдельный сервис.
Когда нужен Elasticsearch
- Таблицы от 10–50 млн записей с активным поиском.
- Требуется фасетный поиск (фильтры по цене, бренду, рейтингу).
- Нечёткий поиск, учёт опечаток, фонетическое сходство.
- Многоязычный стемминг и синонимы.
- Автодополнение и поиск по мере набора (suggest API).
- Горизонтальное масштабирование индекса по шардам.
- Аналитика и агрегации поверх текстовых данных.
Бенчмарки ориентира (2025–2026)
В независимых тестах на таблице из 10 млн статей (средний размер документа ~2 КБ, utf8mb4):
- MySQL 8.4 InnoDB FTS: медианный latency простого запроса — 15–40 мс, p99 — 120–300 мс при конкурентности 50 соединений.
- Elasticsearch 8.x (3 ноды, 32 GB heap): медианный latency — 5–15 мс, p99 — 40–80 мс при той же нагрузке.
- При добавлении фасетов (aggregations) MySQL не имеет нативного аналога; Elasticsearch отвечает за 20–60 мс дополнительно.
Вывод: на малых объёмах разница незначительна. На 10+ млн документов Elasticsearch быстрее в 3–5 раз, а с фасетами — несравнимо.
Гибридная стратегия для PHP и Laravel-приложений
Наиболее прагматичный подход для PHP/Laravel-проектов на старте — использовать MySQL FTS, заложив архитектуру с возможностью замены. Laravel предоставляет пакет Laravel Scout, который абстрагирует движок поиска.
// Подключение Scout в модели (Laravel)
use Laravel\Scout\Searchable;
class Article extends Model
{
use Searchable;
public function toSearchableArray(): array
{
return [
'id' => $this->id,
'title' => $this->title,
'body' => $this->body,
'category' => $this->category?->name,
];
}
// Для MySQL FTS через scout-mysql-driver
public function searchableAs(): string
{
return 'articles'; // имя таблицы / индекса
}
}
// Поиск через Scout (драйвер прозрачно заменяется)
$results = Article::search('MySQL полнотекстовый поиск')
->where('active', 1)
->orderBy('published_at', 'desc')
->paginate(20);
Для MySQL-драйвера Scout используйте пакет laravel/scout со встроенной поддержкой «database» driver (Laravel 9+). При росте нагрузки достаточно установить laravel-scout-elastic и переключить SCOUT_DRIVER=elastic в .env — код контроллеров не меняется.
Практические рекомендации по миграции
- Начните с MySQL FTS и Scout database driver — это бесплатно и просто.
- Мониторьте slow query log: запросы к
FULLTEXT-индексу дольше 100 мс — сигнал к действию. - При переходе на Elasticsearch синхронизируйте данные через очереди Laravel (jobs +
Searchable::makeAllSearchable()). - Для кириллицы в Elasticsearch настройте анализатор
russian— он включает стемминг и стоп-слова из коробки. - Не удаляйте
FULLTEXT-индекс после миграции сразу: держите его как fallback 2–4 недели.
Тонкая настройка my.cnf для продакшна
[mysqld]
# Размер буфера для FTS-индексов InnoDB
innodb_ft_cache_size = 32000000
innodb_ft_total_cache_size = 640000000
# Минимальный размер токена
innodb_ft_min_token_size = 2
# Ngram: размер биграммы
ngram_token_size = 2
# Отключаем встроенные стоп-слова
innodb_ft_enable_stopword = OFF
# Логируем медленные запросы (включая FTS)
slow_query_log = ON
long_query_time = 0.1
log_queries_not_using_indexes = ON
Заключение
MySQL Full-Text Search в 2026 году — это зрелый, хорошо документированный инструмент для поиска в умеренных масштабах. Ngram-парсер закрывает базовые потребности поиска на кириллице, булев режим даёт гибкость запросов, а интеграция с Laravel Scout позволяет менять движок без переписывания бизнес-логики.
Однако не стоит питать иллюзий: как только ваш каталог перевалит за несколько миллионов документов, появятся требования к фасетам или нечёткому поиску — MySQL FTS станет узким местом. Elasticsearch — не серебряная пуля, это дополнительная инфраструктура со своей операционной сложностью. Выбирайте осознанно, опираясь на реальные метрики, а не на хайп.
Лучший инструмент поиска — тот, который вы способны эксплуатировать надёжно. Начните с MySQL, измеряйте, масштабируйтесь обоснованно.
Технологии
Теги
Руслан Исмаилов
Senior Web / Backend разработчик. Senior web/backend разработчик с 9-летним опытом. Стек: PHP, Laravel, PostgreSQL, Redis, Docker, Kubernetes, REST, микросервисы, CI/CD. Подробнее обо мне →