Parser ngram

El parser ngram es un parser alternativo para índices full-text que divide el texto en secuencias de N caracteres consecutivos en lugar de separarlo por palabras delimitadas por espacios. Mientras que el parser por defecto de MySQL funciona bien para idiomas occidentales donde las palabras se separan con espacios, resulta completamente ineficaz para idiomas como chino, japonés y coreano (conocidos como CJK), que escriben palabras de forma continua sin separadores. El parser ngram resuelve este problema y además ofrece una ventaja adicional: permite buscar subcadenas dentro de palabras en cualquier idioma.

Desde MySQL 5.7.6, el parser ngram viene integrado de forma nativa como plugin del servidor. No necesitas instalar nada adicional ni compilar extensiones. Basta con especificar WITH PARSER ngram al crear el índice full-text para activarlo. Esta integración nativa garantiza un rendimiento óptimo y compatibilidad total con las funciones de búsqueda full-text como MATCH ... AGAINST en todos sus modos: lenguaje natural, booleano y expansión de consulta.

Qué es un ngram

Un ngram es una secuencia de N caracteres consecutivos extraída de un texto. El concepto proviene de la lingüística computacional y el procesamiento de lenguaje natural, donde se utiliza ampliamente para análisis de texto, predicción de palabras y detección de idiomas. En el contexto de MySQL, los ngramas se usan como tokens para el índice full-text, reemplazando las palabras completas que usa el parser por defecto.

Con un tamaño de token de 2 (el valor por defecto, conocido como "bigrama"), la palabra "MySQL" se divide en los siguientes tokens:

TokenPosición
My1-2
yS2-3
SQ3-4
QL4-5

Esto permite buscar subcadenas dentro de palabras, algo que el parser por defecto no puede hacer. Si buscas "SQL", el parser ngram lo divide en los tokens "SQ" y "QL", que coinciden con los tokens generados a partir de "MySQL", "PostgreSQL" o "NoSQL". Con el parser por defecto, buscar "SQL" no encontraría ninguno de estos términos porque los trata como palabras completas y diferentes.

El tamaño del ngram determina la granularidad de la indexación. Un tamaño de 1 (unigrama) genera un token por cada carácter individual, lo que maximiza la capacidad de encontrar coincidencias pero produce índices muy grandes. Un tamaño de 2 (bigrama) ofrece un buen equilibrio entre precisión y tamaño del índice. Tamaños mayores reducen el número de tokens pero requieren que la consulta de búsqueda sea más larga para generar coincidencias.

Sintaxis

Para crear un índice full-text con el parser ngram, se añade la cláusula WITH PARSER ngram al final de la definición del índice. Esta cláusula se puede usar tanto en sentencias CREATE TABLE como en ALTER TABLE o CREATE INDEX.

-- Crear índice con parser ngram sobre una tabla existente
CREATE FULLTEXT INDEX nombre_indice ON tabla (columna) WITH PARSER ngram;
 
-- Incluir el índice en la definición de la tabla
CREATE TABLE tabla (
    id INT AUTO_INCREMENT PRIMARY KEY,
    contenido TEXT,
    FULLTEXT INDEX ft_contenido (contenido) WITH PARSER ngram
);
 
-- Añadir índice ngram a una tabla existente con ALTER TABLE
ALTER TABLE tabla ADD FULLTEXT INDEX ft_contenido (contenido) WITH PARSER ngram;

Ten en cuenta que una misma columna puede tener un índice full-text con el parser por defecto y otro con el parser ngram simultáneamente, aunque esto duplica el espacio de almacenamiento. En la práctica, lo habitual es elegir uno u otro según las necesidades de búsqueda de tu aplicación.

Configuración del tamaño del token

El tamaño del token ngram se controla mediante la variable de sistema ngram_token_size. Este valor determina cuántos caracteres consecutivos forman cada token del índice. Puedes consultar el valor actual con la siguiente sentencia.

SHOW VARIABLES LIKE 'ngram_token_size';
Variable_nameValue
ngram_token_size2

El valor por defecto es 2, que es adecuado para la mayoría de escenarios. Es importante saber que esta variable es de solo lectura en tiempo de ejecución: se configura al iniciar el servidor MySQL y no se puede cambiar dinámicamente con SET. Para modificarla, debes editar el archivo de configuración del servidor y reiniciar.

# En my.cnf (Linux/macOS) o my.ini (Windows):
[mysqld]
ngram_token_size=2

Después de cambiar este valor, es imprescindible reconstruir todos los índices full-text que usen el parser ngram, ya que los tokens almacenados ya no coincidirán con el nuevo tamaño. Puedes reconstruir un índice con ALTER TABLE tabla ENGINE=InnoDB o con OPTIMIZE TABLE tabla.

La elección del tamaño del token depende del idioma del contenido y del tipo de búsqueda que necesites:

Token sizeUso recomendadoEjemplo
1Chino, japonés (cada carácter es significativo)"数" encuentra "数据库"
2Chino, japonés, coreano, subcadenas generales"数据" encuentra "数据库管理"
3Búsquedas más específicas, menos falsos positivos"SQL" como token completo
4+Códigos, identificadores, términos técnicos largosCoincidencias más precisas

Un token size mayor produce un índice más pequeño y búsquedas más rápidas, pero exige que el término de búsqueda tenga al menos esa longitud para generar tokens. Con ngram_token_size=2, una búsqueda de un solo carácter no generará ningún token y no devolverá resultados.

Ejemplo con datos en español

Aunque el parser ngram se diseñó originalmente para idiomas CJK, resulta extremadamente útil para búsquedas de subcadenas en español y otros idiomas occidentales. Veamos un ejemplo práctico con una tabla de artículos técnicos donde queremos encontrar documentos que mencionen tecnologías de bases de datos, incluso cuando el término buscado aparece como parte de una palabra más larga.

CREATE TABLE articulos (
    id INT AUTO_INCREMENT PRIMARY KEY,
    titulo VARCHAR(200),
    contenido TEXT,
    fecha_publicacion DATE,
    FULLTEXT INDEX ft_articulos (titulo, contenido) WITH PARSER ngram
);
 
INSERT INTO articulos (titulo, contenido, fecha_publicacion) VALUES
('Introducción a MySQL', 'MySQL es un sistema de gestión de bases de datos relacional de código abierto desarrollado por Oracle.', '2026-01-15'),
('PostgreSQL vs MySQL', 'Comparación detallada entre PostgreSQL y MySQL para aplicaciones web modernas con alto tráfico.', '2026-02-01'),
('MongoDB NoSQL', 'MongoDB es una base de datos NoSQL orientada a documentos que almacena datos en formato BSON.', '2026-02-10'),
('Redis como Cache', 'Redis es un almacén de datos en memoria utilizado como cache y broker de mensajes en arquitecturas distribuidas.', '2026-02-20'),
('Optimización de consultas SQL', 'Técnicas avanzadas para optimizar consultas SQL en bases de datos relacionales con millones de registros.', '2026-03-01');

Ahora podemos buscar "SQL" y el parser ngram encontrará coincidencias incluso dentro de palabras compuestas como "MySQL", "PostgreSQL" y "NoSQL":

SELECT titulo, MATCH(titulo, contenido) AGAINST('SQL' IN BOOLEAN MODE) AS relevancia
FROM articulos
WHERE MATCH(titulo, contenido) AGAINST('SQL' IN BOOLEAN MODE)
ORDER BY relevancia DESC;
titulorelevancia
Optimización de consultas SQL0.4532
PostgreSQL vs MySQL0.3876
Introducción a MySQL0.3214
MongoDB NoSQL0.2198

Con el parser por defecto, buscar "SQL" no encontraría "MySQL" ni "PostgreSQL" porque son palabras completas diferentes. Con ngram, el término "SQL" se divide en los bigramas "SQ" y "QL", que coinciden con los bigramas generados a partir de cualquier palabra que contenga esa secuencia de caracteres. El artículo "Optimización de consultas SQL" obtiene la mayor relevancia porque contiene "SQL" como palabra independiente, donde todos los bigramas coinciden perfectamente.

Ejemplo con idiomas CJK

El caso de uso principal del parser ngram es permitir búsquedas full-text en idiomas que no separan las palabras con espacios. En chino, japonés y coreano, las oraciones se escriben como secuencias continuas de caracteres, y el parser por defecto de MySQL no puede segmentarlas en tokens útiles. El parser ngram soluciona esto dividiendo el texto en secuencias de N caracteres.

CREATE TABLE contenido_cjk (
    id INT AUTO_INCREMENT PRIMARY KEY,
    titulo VARCHAR(200),
    texto VARCHAR(500),
    idioma CHAR(2),
    FULLTEXT INDEX ft_cjk (texto) WITH PARSER ngram
);
 
INSERT INTO contenido_cjk (titulo, texto, idioma) VALUES
('Gestión de bases de datos', '数据库管理系统', 'zh'),
('Análisis de datos e IA', '数据分析与机器学习', 'zh'),
('Tecnología de IA', '人工智能技术', 'zh'),
('Computación en la nube', '云计算服务平台', 'zh'),
('Seguridad de red', '网络安全防护系统', 'zh');

Ahora podemos buscar documentos que contengan el término chino para "datos" (数据):

SELECT titulo, texto FROM contenido_cjk
WHERE MATCH(texto) AGAINST('数据' IN BOOLEAN MODE);
titulotexto
Gestión de bases de datos数据库管理系统
Análisis de datos e IA数据分析与机器学习

Sin el parser ngram, esta búsqueda no funcionaría porque el parser por defecto trataría toda la cadena "数据库管理系统" como un único token (al no encontrar espacios), y la búsqueda de "数据" no coincidiría con ese token completo. Con el parser ngram y un token size de 2, la cadena se divide en bigramas como "数据", "据库", "库管", etc., y la búsqueda de "数据" genera el bigrama "数据" que coincide directamente.

Búsquedas de códigos y SKUs

Un caso de uso práctico que muchos desarrolladores pasan por alto es la búsqueda de códigos de producto, referencias o SKUs parciales. En un sistema de inventario, es habitual que los usuarios busquen un producto escribiendo solo parte de su código. Con el parser por defecto esto es imposible, pero con ngram funciona perfectamente.

CREATE TABLE inventario (
    id INT AUTO_INCREMENT PRIMARY KEY,
    codigo VARCHAR(50),
    nombre VARCHAR(200),
    descripcion TEXT,
    FULLTEXT INDEX ft_inventario (codigo, nombre, descripcion) WITH PARSER ngram
);
 
INSERT INTO inventario (codigo, nombre, descripcion) VALUES
('ELEC-TV-SAM-55', 'Samsung Smart TV 55"', 'Televisor Samsung de 55 pulgadas con resolución 4K UHD'),
('ELEC-TV-LG-65', 'LG OLED TV 65"', 'Televisor LG OLED de 65 pulgadas con tecnología Dolby Vision'),
('ELEC-MOV-SAM-S24', 'Samsung Galaxy S24', 'Smartphone Samsung Galaxy S24 con cámara de 200 MP'),
('ELEC-MOV-APP-15P', 'iPhone 15 Pro', 'Smartphone Apple iPhone 15 Pro con chip A17 Pro'),
('HOGAR-ASPI-DYS-V15', 'Dyson V15 Detect', 'Aspirador inalámbrico Dyson con sensor láser de partículas');
 
SELECT codigo, nombre
FROM inventario
WHERE MATCH(codigo, nombre, descripcion) AGAINST('SAM' IN BOOLEAN MODE);
codigonombre
ELEC-TV-SAM-55Samsung Smart TV 55"
ELEC-MOV-SAM-S24Samsung Galaxy S24

La búsqueda de "SAM" encuentra tanto los productos cuyo código contiene "SAM" como aquellos cuyo nombre o descripción incluyen "Samsung", ya que "SAM" genera bigramas que coinciden con partes de estas cadenas.

ngram vs parser por defecto

Entender las diferencias entre ambos parsers te ayudará a elegir el adecuado para cada situación. La elección no es trivial, ya que impacta directamente en el tamaño del índice, la velocidad de las búsquedas y el tipo de coincidencias que se encuentran.

CaracterísticaParser por defectoParser ngram
Separación de tokensPor espacios y puntuaciónPor N caracteres consecutivos
Idiomas CJKNo funciona correctamenteFunciona perfectamente
Búsqueda de subcadenasNo soportadaSoportada nativamente
Tamaño del índiceMenor (menos tokens)Mayor (más tokens por palabra)
Velocidad de búsquedaMás rápidoMás lento por mayor volumen de tokens
StopwordsAplica lista de stopwordsAplica a tokens individuales
Búsqueda con comodinesSoportada con *Limitada (los ngramas ya cubren subcadenas)
Longitud mínima de búsquedaConfigurable con ft_min_word_lenDeterminada por ngram_token_size

El parser ngram genera significativamente más tokens que el parser por defecto. Por ejemplo, la palabra "MySQL" genera un solo token con el parser por defecto pero cuatro bigramas con el parser ngram. En tablas con millones de registros y columnas de texto extenso, esta diferencia se traduce en índices considerablemente más grandes y tiempos de escritura más lentos. Evalúa cuidadosamente si realmente necesitas búsqueda de subcadenas antes de usar ngram en tablas de gran volumen. Para optimizar el rendimiento en ambos casos, consulta las técnicas de rendimiento full-text.

ngram en modo booleano

El parser ngram es totalmente compatible con el modo booleano de búsqueda full-text, lo que permite combinar operadores como + (obligatorio), - (excluir) y comillas para frases exactas.

Para excluir resultados que contengan cierto término, usa el operador -:

SELECT titulo FROM articulos
WHERE MATCH(titulo, contenido) AGAINST('+MySQL -PostgreSQL' IN BOOLEAN MODE);
titulo
Introducción a MySQL

Esta consulta encuentra artículos que contienen "MySQL" pero no "PostgreSQL". El operador + obliga a que los bigramas de "MySQL" estén presentes, mientras que - excluye las filas donde aparecen los bigramas de "PostgreSQL".

Para buscar una frase exacta, enciérrala entre comillas dobles:

SELECT titulo FROM articulos
WHERE MATCH(titulo, contenido) AGAINST('"bases de datos"' IN BOOLEAN MODE);
titulo
Introducción a MySQL
Optimización de consultas SQL

Con el parser ngram, la búsqueda de frases exactas funciona verificando que los bigramas aparezcan en la secuencia correcta y en posiciones adyacentes. Esto garantiza que "bases de datos" coincida solo cuando esas palabras aparecen juntas y en ese orden.

Stopwords y el parser ngram

El manejo de stopwords en el parser ngram difiere del parser por defecto. Con el parser estándar, se eliminan las palabras comunes como "de", "la", "en" antes de indexar. Con el parser ngram, las stopwords se aplican a nivel de token: si un bigrama completo coincide con una stopword, se descarta. Pero si una stopword genera bigramas que forman parte de una palabra mayor, esos bigramas se conservan.

En la práctica, esto significa que el parser ngram es más permisivo con las stopwords. La palabra "de" como bigrama se descartaría, pero los bigramas "de" dentro de "desarrollo" o "modelo" se mantienen porque forman parte de tokens generados por palabras más largas.

Para ver la lista de stopwords configuradas, puedes consultar la tabla del sistema:

SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD;

Cuándo usar el parser ngram

El parser ngram es la elección correcta en varios escenarios concretos. El más obvio es cuando tu contenido está en chino, japonés o coreano, donde es prácticamente imprescindible para que la búsqueda full-text funcione. También es ideal cuando necesitas buscar subcadenas dentro de palabras, como códigos de producto, identificadores parciales o términos técnicos que pueden aparecer como parte de palabras compuestas.

Sin embargo, no siempre es la mejor opción. Si tu contenido está exclusivamente en idiomas occidentales con separación por espacios y la búsqueda por palabras completas es suficiente, el parser por defecto será más eficiente en espacio y velocidad. En tablas muy grandes donde el tamaño del índice es un factor crítico, el incremento sustancial del índice ngram puede ser un problema. Y si ya tienes una solución de búsqueda externa como Elasticsearch o Solr, duplicar esa funcionalidad con ngram en MySQL puede ser redundante.

La recomendación general es empezar con el parser por defecto y migrar a ngram solo cuando detectes que necesitas alguna de sus capacidades únicas: soporte CJK, búsqueda de subcadenas o coincidencias parciales en códigos e identificadores.

Limpieza

Para eliminar las tablas creadas en los ejemplos de este artículo, ejecuta:

DROP TABLE IF EXISTS articulos;
DROP TABLE IF EXISTS contenido_cjk;
DROP TABLE IF EXISTS inventario;

Con esto completamos la sección de búsqueda full-text. Hemos cubierto cómo crear índices full-text, optimizar su rendimiento, usar MATCH AGAINST en sus diferentes modos —incluyendo búsqueda en lenguaje natural y modo booleano—, la expansión de consulta y el parser ngram para idiomas especiales y búsquedas de subcadenas. En la siguiente sección exploraremos las vistas en MySQL.

Escrito por Eduardo Lázaro