Búsquedas de texto completo en MySQL 8: FULLTEXT con InnoDB y modo booleano

Hay una búsqueda que tarde o temprano necesita cualquier aplicación que maneje texto: el usuario escribe «base datos relacional» en un buscador interno y espera encontrar artículos relevantes, no solo los que contengan exactamente esa cadena en ese orden. LIKE '%base datos relacional%' no lo va a resolver. Para eso existen los índices FULLTEXT de MySQL, que este artículo explica desde sus fundamentos hasta los operadores avanzados del modo booleano.

El texto original fue cedido por el equipo de MySQL Hispano y publicado en 2003. En aquel momento los índices FULLTEXT solo funcionaban con tablas MyISAM y MySQL 4.0.14 era la versión de referencia. Esta edición, actualizada por David Carrero en 2026, incorpora las mejoras introducidas desde MySQL 5.6 (soporte FULLTEXT en InnoDB) y los parámetros actuales de configuración.

Por qué LIKE no es suficiente

Una consulta con LIKE 'arte%' puede apoyarse en un índice de árbol-B y funciona razonablemente bien. El problema llega cuando la cadena puede aparecer en cualquier posición:

SELECT * FROM articulos WHERE contenido LIKE '%arte%';

MySQL no puede usar un índice normal aquí. Tiene que leer cada fila y examinar el valor completo de la columna. Con tablas grandes, esto escala fatal.

Hay dos problemas añadidos que LIKE no resuelve bien:

  1. Ruido. LIKE '%arte%' devuelve «artefacto», «pegarte», «quejarte»… Resultados que no tienen nada que ver con «arte».
  2. Relevancia. LIKE no distingue entre un documento donde «arte» aparece 20 veces y otro donde aparece una sola vez. Los índices FULLTEXT sí calculan un score de relevancia.

FULLTEXT con InnoDB: la gran novedad de MySQL 5.6

El artículo original (2003) indicaba que los índices FULLTEXT «se usan en tablas del tipo MyISAM». Esto fue así hasta MySQL 5.6, cuando InnoDB incorporó soporte nativo para FULLTEXT. A partir de MySQL 5.6 (y en todas las versiones actuales, incluyendo MySQL 8), puedes crear índices FULLTEXT sobre columnas CHAR, VARCHAR y TEXT en tablas InnoDB.

En la práctica, esto significa que puedes combinar búsquedas de texto completo con transacciones, claves foráneas e integridad referencial, algo imposible con MyISAM.

Crear un índice FULLTEXT

Hay tres formas equivalentes. Al crear la tabla:

CREATE TABLE articulos (
  id       INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  titulo   VARCHAR(200) NOT NULL,
  contenido TEXT,
  FULLTEXT indice_tc (titulo, contenido)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;

Sobre una tabla ya existente:

CREATE FULLTEXT INDEX indice_tc ON articulos (titulo, contenido);

O con ALTER TABLE:

ALTER TABLE articulos ADD FULLTEXT indice_tc (titulo, contenido);

Si la tabla tiene muchos datos, es más rápido cargarlos primero sin el índice y crearlo después con ALTER TABLE. Añadir datos a una tabla con un índice FULLTEXT ya existente es más lento porque el índice se actualiza en cada inserción.

MATCH() y AGAINST(): búsqueda en modo natural

Las búsquedas FULLTEXT se hacen con la función MATCH() y el argumento AGAINST():

SELECT id, titulo
FROM articulos
WHERE MATCH(titulo, contenido) AGAINST('MySQL Java');

Esta consulta devuelve los artículos que contienen alguna de las palabras «MySQL» o «Java», ordenados automáticamente por relevancia descendente (los más relevantes primero).

Dos comportamientos importantes que conviene conocer:

  • Regla del 50%. Las palabras que aparecen en más del 50% de las filas se consideran «ruido» y no se indexan en el modo natural. Si todos tus artículos contienen la palabra «MySQL», una búsqueda de «MySQL» en modo natural no devolverá nada. En modo booleano (ver más abajo) esta restricción no existe.
  • Longitud mínima. Por defecto, las palabras de 3 caracteres o menos no se indexan. Esto explica por qué buscar «PHP» con una tabla de pocos registros puede devolver cero resultados: «PHP» tiene solo 3 letras. El parámetro que controla esto (en InnoDB) es innodb_ft_min_token_size (por defecto: 3). En MyISAM era ft_min_word_len.

Relevancia: cómo MySQL puntúa los resultados

Cada fila recibe un valor de relevancia numérico. Para verlo explícitamente:

SELECT id, titulo,
  MATCH(titulo, contenido) AGAINST('Java Linux') AS relevancia
FROM articulos
ORDER BY relevancia DESC;

El cálculo tiene en cuenta:

  • El número de veces que aparece el término en la fila.
  • El número total de palabras en la fila (documentos más cortos donde el término aparece tienen mayor densidad).
  • El número de documentos que contienen el término (términos raros son más discriminativos).

Un artículo donde aparecen tanto «Java» como «Linux» tendrá una relevancia más alta que uno que solo contenga «Java».

IN BOOLEAN MODE: control fino sobre la búsqueda

El modo booleano, disponible desde MySQL 4.0.1, permite controlar exactamente qué palabras deben aparecer y cuáles no:

SELECT id, titulo
FROM articulos
WHERE MATCH(titulo, contenido) AGAINST('+Java -Visual' IN BOOLEAN MODE);

Esta consulta devuelve filas que contienen «Java» pero no contienen «Visual».

El modo booleano no ordena automáticamente por relevancia, pero tampoco tiene la restricción del 50% ni la de longitud mínima de la forma estricta del modo natural.

Los operadores disponibles en modo booleano:

Operador

Significado

Ejemplo

+

La palabra debe estar presente

+mysql +linux

-

La palabra no debe estar presente

+mysql -windows

(sin operador)

La palabra es opcional (aumenta relevancia si aparece)

mysql java

>

Aumenta la contribución a la relevancia

+mysql >linux

<

Disminuye la contribución a la relevancia

+mysql <windows

~

Negación suave: resta relevancia si aparece

mysql ~windows

*

Comodín al final de la palabra

mysql* (mysql, mysqldumpÂ…)

""

Búsqueda de frase exacta

"MySQL con Java"

( )

Agrupación de subexpresiones

+mysql +(linux windows)

Algunos ejemplos prácticos:

-- Debe contener mysql Y linux
SELECT * FROM articulos WHERE MATCH(titulo, contenido)
AGAINST('+mysql +linux' IN BOOLEAN MODE);

-- Debe contener mysql, mejor si también tiene java
SELECT * FROM articulos WHERE MATCH(titulo, contenido)
AGAINST('+mysql java' IN BOOLEAN MODE);

-- Puede ser cualquier variante de mysql: mysqldump, mysqladmin, etc.
SELECT * FROM articulos WHERE MATCH(titulo, contenido)
AGAINST('mysql*' IN BOOLEAN MODE);

-- Frase exacta
SELECT * FROM articulos WHERE MATCH(titulo, contenido)
AGAINST('"MySQL con Java en Linux"' IN BOOLEAN MODE);

WITH QUERY EXPANSION: ampliar la búsqueda automáticamente

MySQL ofrece un tercer modo menos conocido: WITH QUERY EXPANSION. Primero ejecuta la búsqueda en modo natural, toma las palabras más relevantes de los primeros resultados, y lanza una segunda búsqueda ampliada con esos términos. Útil cuando los usuarios escriben búsquedas muy cortas:

SELECT id, titulo
FROM articulos
WHERE MATCH(titulo, contenido) AGAINST('Java' WITH QUERY EXPANSION);

Tiene el riesgo de devolver resultados poco relacionados si los primeros documentos encontrados son muy heterogéneos.

Configurar parámetros FULLTEXT en InnoDB

A diferencia de MyISAM (que usaba ft_min_word_len y ft_stopword_file), InnoDB tiene sus propias variables para FULLTEXT:

SHOW VARIABLES LIKE 'innodb_ft%';

Variable

Por defecto

Descripción

innodb_ft_min_token_size

3

Longitud mínima de token para indexar. Bajar a 2 indexa también palabras de 2 letras.

innodb_ft_max_token_size

84

Longitud máxima de token.

innodb_ft_enable_stopword

ON

Activa/desactiva la lista de stopwords.

innodb_ft_server_stopword_table

NULL

Tabla personalizada de stopwords (formato: base/tabla).

innodb_ft_user_stopword_table

NULL

Stopwords por conexión.

Si cambias innodb_ft_min_token_size necesitas reconstruir los índices FULLTEXT. Para eso, la forma más sencilla en InnoDB es:

ALTER TABLE articulos DROP INDEX indice_tc;
ALTER TABLE articulos ADD FULLTEXT INDEX indice_tc (titulo, contenido);

(El REPAIR TABLE ... QUICK del artículo original funciona solo con MyISAM.)

Restricciones actuales de FULLTEXT

  • Todos los parámetros de MATCH() deben pertenecer al mismo índice FULLTEXT, salvo en modo booleano.
  • La lista de columnas en MATCH() debe coincidir exactamente con la definición del índice, salvo en modo booleano.
  • El argumento de AGAINST() debe ser una cadena constante (no una columna ni una subquery).
  • Las transacciones no se propagan al índice FULLTEXT de InnoDB de forma sincronizada en todas las versiones; el índice auxiliar se actualiza en background.

Combinando FULLTEXT con filtros convencionales

FULLTEXT y las condiciones SQL normales se pueden mezclar en la misma consulta:

SELECT id, titulo,
  MATCH(titulo, contenido) AGAINST('+mysql +innodb' IN BOOLEAN MODE) AS score
FROM articulos
WHERE activo = 1
  AND fecha_publicacion >= '2020-01-01'
  AND MATCH(titulo, contenido) AGAINST('+mysql +innodb' IN BOOLEAN MODE)
ORDER BY score DESC
LIMIT 20;

Esta es la forma habitual de implementar un buscador interno: combinas el score de relevancia FULLTEXT con filtros por categoría, fecha, estado de publicación u otros campos.

Para el buscador de programacion.net, los artículos sobre búsquedas PHP+MySQL relacionadas pueden encontrarse en Programar un buscador con PHP y MySQL.

Imagen: Pexels / cottonbro studio

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP