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:
- Ruido.
LIKE '%arte%'devuelve «artefacto», «pegarte», «quejarte»… Resultados que no tienen nada que ver con «arte». - 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 eraft_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 |
|
| La palabra no debe estar presente |
|
(sin operador) | La palabra es opcional (aumenta relevancia si aparece) |
|
| Aumenta la contribución a la relevancia |
|
| Disminuye la contribución a la relevancia |
|
| Negación suave: resta relevancia si aparece |
|
| ComodÃn al final de la palabra |
|
| Búsqueda de frase exacta |
|
| Agrupación de subexpresiones |
|
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 |
| 3 | Longitud mÃnima de token para indexar. Bajar a 2 indexa también palabras de 2 letras. |
| 84 | Longitud máxima de token. |
| ON | Activa/desactiva la lista de stopwords. |
| NULL | Tabla personalizada de stopwords (formato: |
| 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
