Базы данных

MySQL Full-Text Search в 2026 году: возможности, ограничения и сравнение с Elasticsearch

Ruslan Ismailov Опубликовано 14 мин чтения
M

Введение: когда встроенного поиска достаточно, а когда нет

В 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 — код контроллеров не меняется.

Практические рекомендации по миграции

  1. Начните с MySQL FTS и Scout database driver — это бесплатно и просто.
  2. Мониторьте slow query log: запросы к FULLTEXT-индексу дольше 100 мс — сигнал к действию.
  3. При переходе на Elasticsearch синхронизируйте данные через очереди Laravel (jobs + Searchable::makeAllSearchable()).
  4. Для кириллицы в Elasticsearch настройте анализатор russian — он включает стемминг и стоп-слова из коробки.
  5. Не удаляйте 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. Подробнее обо мне →