Bases de datos

MySQL Full-Text Search en 2026: capacidades, limitaciones y comparación con Elasticsearch

Ruslan Ismailov Publicado 14 min de lectura
M

Introducción: cuándo la búsqueda integrada es suficiente y cuándo no

En 2026, la pregunta «¿usar MySQL Full-Text Search o integrar Elasticsearch?» sigue siendo relevante para cientos de miles de proyectos. Muchos equipos adoptan Elasticsearch por inercia, sin verificar si la búsqueda nativa de MySQL puede manejar la carga real. Otros, en cambio, alcanzan el límite de los índices FULLTEXT cuando el catálogo crece hasta millones de registros.

Una heurística sencilla: si tienes hasta 5–10 millones de filas, las consultas de búsqueda no son más complejas que condiciones booleanas y los requisitos de relevancia son moderados, MySQL Full-Text Search resolverá la tarea sin infraestructura adicional. En cuanto aparezcan requisitos de búsqueda facetada, sinónimos, stemming multilingüe o indexación en streaming, es momento de considerar Elasticsearch.

Tipos de índices Full-Text y modos de búsqueda en MySQL

Índice FULLTEXT

MySQL admite índices FULLTEXT únicamente para tablas InnoDB y MyISAM. El índice puede crearse al crear la tabla o posteriormente:

-- Al crear la tabla
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;

-- Añadir a una tabla existente
ALTER TABLE articles ADD FULLTEXT INDEX idx_fts (title, body);

-- O mediante CREATE INDEX
CREATE FULLTEXT INDEX idx_fts ON articles (title, body);

MATCH...AGAINST y modos de búsqueda

El operador MATCH(col1, col2) AGAINST('query' IN MODE) es la única forma de utilizar un índice FULLTEXT. MySQL admite tres modos:

  • IN NATURAL LANGUAGE MODE — modo predeterminado. MySQL divide la consulta en tokens, excluye las palabras vacías y clasifica los resultados mediante un algoritmo similar a TF-IDF.
  • IN BOOLEAN MODE — búsqueda booleana con operadores +, -, *, "", >, <. Permite construir consultas complejas sin relevancia predeterminada.
  • WITH QUERY EXPANSION — búsqueda en dos pasadas: primero encuentra documentos relevantes, luego amplía la consulta con palabras de esos documentos. Aumenta la exhaustividad, pero puede reducir la precisión.
-- NATURAL LANGUAGE MODE
SELECT id, title,
       MATCH(title, body) AGAINST('búsqueda de texto completo MySQL') AS score
FROM articles
WHERE MATCH(title, body) AGAINST('búsqueda de texto completo MySQL')
ORDER BY score DESC
LIMIT 20;

-- BOOLEAN MODE: palabra obligatoria, exclusión, frase
SELECT id, title
FROM articles
WHERE MATCH(title, body) AGAINST('+MySQL -Oracle "búsqueda de texto completo"' IN BOOLEAN MODE);

-- WITH QUERY EXPANSION
SELECT id, title
FROM articles
WHERE MATCH(title, body) AGAINST('indexación' WITH QUERY EXPANSION)
LIMIT 10;

Configuración y optimización de índices FTS

Longitud mínima de palabra

Por defecto, MySQL indexa palabras de 3 o más caracteres (innodb_ft_min_token_size = 3 para InnoDB, ft_min_word_len = 4 para MyISAM). Para contenido en español esto puede ser un problema: palabras cortas pero significativas («año», «web», «SQL») quedan fuera del índice.

# my.cnf / my.ini
[mysqld]
# Para InnoDB
innodb_ft_min_token_size = 2

# Para MyISAM (si se usa)
ft_min_word_len = 2

# Desactivamos las palabras vacías integradas (o indicamos nuestro propio archivo)
innodb_ft_enable_stopword = OFF
# innodb_ft_server_stopword_table = 'mydb/my_stopwords'

Tras cambiar los parámetros es necesario reconstruir el índice: ejecutar OPTIMIZE TABLE articles; o recrear el índice FULLTEXT.

Parser ngram para caracteres especiales y CJK

El parser integrado estándar tiene limitaciones con idiomas no anglosajones: no realiza análisis morfológico y depende de los espacios como separadores. Para la búsqueda por subcadenas se recomienda el parser ngram, disponible desde MySQL 5.7.

# my.cnf
[mysqld]
ngram_token_size = 2
-- Creación de índice con parser 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;

-- Búsqueda por subcadena
SELECT id, name
FROM products
WHERE MATCH(name, description) AGAINST('smartph' IN BOOLEAN MODE);

El índice ngram divide el texto en todos los N-gramas posibles del tamaño especificado. Con ngram_token_size = 2, la palabra «Madrid» generará bigramas: «ma», «ad», «dr», «ri», «id». Esto permite buscar por cualquier subcadena, pero aumenta el tamaño del índice entre 3 y 5 veces respecto al parser estándar.

Palabras vacías (stopwords)

MySQL incluye una lista de palabras vacías en inglés. Para proyectos en español es necesario desactivar la lista estándar o utilizar una propia:

-- Creamos la tabla de palabras vacías
CREATE TABLE mydb.stopwords (
  value VARCHAR(30) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO mydb.stopwords VALUES
  ('y'), ('en'), ('de'), ('la'), ('el'), ('los'), ('las'), ('un');

-- En my.cnf indicamos la ruta a la tabla:
-- innodb_ft_server_stopword_table = 'mydb/stopwords'

Ejemplos SQL prácticos para escenarios de búsqueda complejos

Búsqueda con filtrado y ordenación por relevancia

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;

Boosting por campo: el título es más importante que la descripción

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;

Atención: para este truco son necesarios dos índices FULLTEXT separados — uno sobre title y otro sobre body.

Búsqueda con tolerancia a errores tipográficos mediante SOUNDEX (solución alternativa)

-- Aproximación básica: FTS + filtro SOUNDEX
SELECT id, title
FROM articles
WHERE MATCH(title, body) AGAINST(:query IN BOOLEAN MODE)
   OR SOUNDEX(title) = SOUNDEX(:query)
LIMIT 20;

SOUNDEX en MySQL está orientado al inglés y funciona mal con otros idiomas. Para búsqueda difusa en español es preferible considerar Elasticsearch o un preprocesamiento en el lado de PHP/Laravel.

Relevancia y clasificación: cómo calcula MySQL el score

En el modo IN NATURAL LANGUAGE MODE, MySQL calcula el score con una fórmula similar a TF-IDF: cuanto más frecuente es la palabra en el documento (TF) y más rara en toda la colección (IDF), mayor es su peso. Los documentos en los que la palabra buscada aparece en más del 50% de las filas reciben un score cero y no se devuelven — una trampa habitual con conjuntos de datos pequeños (menos de 20 filas).

Para mejorar la calidad de la clasificación en MySQL se aplican varias técnicas:

  • Separar índices por campos con distinto peso (title, tags, body) y sumar scores con coeficientes.
  • Almacenar un «boost» precalculado del documento (valoración, número de visitas) y multiplicarlo por el score FTS.
  • Usar IN BOOLEAN MODE para controlar explícitamente los pesos con los operadores > y <.
-- Boost explícito mediante el operador > en BOOLEAN MODE
SELECT id, title
FROM articles
WHERE MATCH(title, body)
  AGAINST('(>MySQL 

Limitaciones de MySQL FTS: análisis honesto

Escalabilidad

El índice de texto completo en InnoDB se almacena en tablas auxiliares separadas. Con escrituras activas, la actualización del índice ocurre a través de un buffer (FTS_DOC_ID, lista DELETED), lo que con un alto flujo DML provoca el crecimiento del hilo de merge en segundo plano y caídas de rendimiento. En tablas de más de 50 millones de filas, la indexación se ralentiza notablemente.

Relevancia

TF-IDF sin morfología, sin sinónimos y sin considerar la posición de la palabra en el documento es una limitación importante. Para búsquedas en e-commerce con errores tipográficos, transliteración y sinónimos, MySQL FTS pierde frente a Elasticsearch de forma estructural.

Soporte de idiomas

Sin parsers de terceros, MySQL no realiza stemming: «correr», «corriendo», «corrió» son tokens distintos. El parser ngram soluciona el problema parcialmente mediante búsqueda por subcadenas, pero no es un sustituto completo de un analizador morfológico.

Otras limitaciones

  • No hay búsqueda facetada (aggregations).
  • No hay búsqueda en estructuras anidadas ni en JSON sin esfuerzo adicional.
  • El índice FULLTEXT no es compatible con columnas de tipo JSON directamente.
  • No hay soporte nativo de sinónimos.
  • La longitud mínima del token ngram tiene un límite inferior (normalmente 1–2 caracteres), lo que afecta al tamaño del índice.

Comparación con Elasticsearch: cuándo migrar y cuándo quedarse

A continuación, un análisis honesto según los criterios clave.

Cuándo MySQL FTS es suficiente

  • Base de datos de hasta 5–10 millones de documentos con carga de lectura moderada.
  • Casos de uso simples: búsqueda en artículos de blog, por SKU, por nombre de usuario.
  • No hay requisitos de facetas, autocompletado ni búsqueda difusa.
  • Equipo sin experiencia en la operación de clústeres Elasticsearch.
  • El presupuesto no permite mantener un servicio independiente.

Cuándo se necesita Elasticsearch

  • Tablas de 10–50 millones de registros con búsquedas activas.
  • Se requiere búsqueda facetada (filtros por precio, marca, valoración).
  • Búsqueda difusa, tolerancia a errores tipográficos, similitud fonética.
  • Stemming multilingüe y sinónimos.
  • Autocompletado y búsqueda mientras se escribe (suggest API).
  • Escalado horizontal del índice mediante shards.
  • Analítica y agregaciones sobre datos textuales.

Benchmarks de referencia (2025–2026)

En pruebas independientes sobre una tabla de 10 millones de artículos (tamaño medio de documento ~2 KB, utf8mb4):

  • MySQL 8.4 InnoDB FTS: latencia mediana de una consulta simple — 15–40 ms, p99 — 120–300 ms con 50 conexiones concurrentes.
  • Elasticsearch 8.x (3 nodos, 32 GB heap): latencia mediana — 5–15 ms, p99 — 40–80 ms con la misma carga.
  • Al añadir facetas (aggregations), MySQL no tiene un equivalente nativo; Elasticsearch responde en 20–60 ms adicionales.

Conclusión: en volúmenes pequeños la diferencia es insignificante. Con 10+ millones de documentos, Elasticsearch es 3–5 veces más rápido, y con facetas la diferencia es incomparable.

Estrategia híbrida para aplicaciones PHP y Laravel

El enfoque más pragmático para proyectos PHP/Laravel al inicio es usar MySQL FTS con una arquitectura que permita sustituirlo. Laravel ofrece el paquete Laravel Scout, que abstrae el motor de búsqueda.

// Integración de Scout en el modelo (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,
        ];
    }

    // Para MySQL FTS mediante scout-mysql-driver
    public function searchableAs(): string
    {
        return 'articles'; // nombre de la tabla / índice
    }
}
// Búsqueda mediante Scout (el driver se reemplaza de forma transparente)
$results = Article::search('MySQL búsqueda de texto completo')
    ->where('active', 1)
    ->orderBy('published_at', 'desc')
    ->paginate(20);

Para el driver MySQL de Scout utiliza el paquete laravel/scout con el soporte integrado del driver «database» (Laravel 9+). Cuando la carga aumente, basta con instalar laravel-scout-elastic y cambiar SCOUT_DRIVER=elastic en .env — el código de los controladores no necesita modificarse.

Recomendaciones prácticas para la migración

  1. Comienza con MySQL FTS y el driver database de Scout — es gratuito y sencillo.
  2. Monitoriza el slow query log: consultas al índice FULLTEXT que superen los 100 ms son una señal de alerta.
  3. Al migrar a Elasticsearch, sincroniza los datos mediante colas de Laravel (jobs + Searchable::makeAllSearchable()).
  4. Para idiomas con morfología compleja en Elasticsearch, configura el analizador correspondiente — incluye stemming y palabras vacías de serie.
  5. No elimines el índice FULLTEXT inmediatamente tras la migración: mantenlo como fallback durante 2–4 semanas.

Ajuste fino de my.cnf para producción

[mysqld]
# Tamaño del buffer para índices FTS de InnoDB
innodb_ft_cache_size = 32000000
innodb_ft_total_cache_size = 640000000

# Tamaño mínimo de token
innodb_ft_min_token_size = 2

# Ngram: tamaño del bigrama
ngram_token_size = 2

# Desactivamos las palabras vacías integradas
innodb_ft_enable_stopword = OFF

# Registramos consultas lentas (incluyendo FTS)
slow_query_log = ON
long_query_time = 0.1
log_queries_not_using_indexes = ON

Conclusión

MySQL Full-Text Search en 2026 es una herramienta madura y bien documentada para búsquedas a escala moderada. El parser ngram cubre las necesidades básicas de búsqueda en distintos idiomas, el modo booleano aporta flexibilidad en las consultas y la integración con Laravel Scout permite cambiar de motor sin reescribir la lógica de negocio.

Sin embargo, no conviene hacerse ilusiones: en cuanto tu catálogo supere varios millones de documentos o aparezcan requisitos de búsqueda facetada o difusa, MySQL FTS se convertirá en el cuello de botella. Elasticsearch no es una bala de plata — es infraestructura adicional con su propia complejidad operativa. Elige con criterio, basándote en métricas reales y no en el hype.

La mejor herramienta de búsqueda es la que puedes operar de forma fiable. Empieza con MySQL, mide y escala con fundamento.

Tecnologías

Etiquetas

Ruslan Ismailov

Desarrollador Senior Web / Backend. Desarrollador senior web/backend con 9 años de experiencia. Stack: PHP, Laravel, PostgreSQL, Redis, Docker, Kubernetes, REST, microservicios, CI/CD. Más sobre mí →