Índices en Bases de Datos MySQL

1. Introducción

En una base de datos, los índices son estructuras auxiliares que mejoran la velocidad de recuperación de datos. La comparación clásica es con el índice de un libro: en vez de leer página por página, buscamos una entrada y vamos directo a la parte que nos interesa.

Cuando una tabla no tiene un índice útil para una consulta, MySQL puede verse obligado a recorrer muchas filas, o incluso toda la tabla. Ese recorrido completo suele asociarse con un full scan. En tablas pequeñas esto puede pasar desapercibido, pero en tablas grandes el costo crece mucho.

Un índice se crea sobre una o más columnas y mantiene una estructura organizada con los valores indexados y referencias que permiten llegar más rápido a las filas reales.

2. Concepto básico

Un índice en MySQL es un objeto de base de datos que puede crearse sobre una o más columnas de una tabla. Su objetivo es permitir un acceso más rápido a los datos en las consultas que utilizan esas columnas.

Por ejemplo, si tenemos miles o millones de clientes y queremos buscar a uno por apellido, sin índice MySQL tendría que revisar fila por fila. Con un índice sobre apellido, puede ubicar mucho más rápido las filas candidatas.

3. ¿Por qué usar índices?

Ventajas principales

Desventajas y costos

Regla de oro: crear índices solo sobre columnas que se usan frecuentemente en WHERE, JOIN, ORDER BY o búsquedas importantes. Indexar “todo” no es una buena práctica.

4. Tipos de índices según su estructura interna

En MySQL, los tipos concretos dependen también del motor de almacenamiento. En la práctica, para InnoDB, la enorme mayoría de los índices que veremos se basan en B-Tree o, más precisamente, en B+Tree.

Tipo Motor compatible Ideal para No sirve bien para
B-Tree / B+Tree InnoDB, MyISAM, MEMORY Búsquedas exactas, por rango, ORDER BY y gran parte de los casos generales Búsquedas de texto completo avanzadas
Full-Text InnoDB, MyISAM Palabras clave en textos largos Igualdad estricta o búsquedas numéricas simples
Hash MEMORY Búsquedas exactas con = o IN Rangos, ORDER BY, búsquedas secuenciales

En la práctica, más del 95% de los índices que se crean en un curso inicial de MySQL serán índices tipo B-Tree.

5. Índices B-Tree

Los índices B-Tree son árboles balanceados. Su objetivo es organizar los valores de forma jerárquica para poder buscar muy rápido sin recorrer toda la tabla.

¿Cómo funcionan?

El árbol está organizado en niveles. Hay una raíz, puede haber nodos intermedios y finalmente nodos hoja. En cada nivel, el índice descarta grandes porciones de datos hasta llegar a la ubicación probable del valor buscado.

La idea se parece a una búsqueda binaria, pero adaptada a estructuras de almacenamiento más complejas y eficientes para bases de datos.

Características

Limitaciones

Representación simplificada de un B-Tree

Raíz: [15, 30]
Nodo 1: [5, 10]
Nodo 2: [20, 25]
Nodo 3: [35, 40]
ID=5
Ana, 24
ID=10
Juan, 30
ID=20
Pedro, 35
ID=25
Laura, 22
ID=35
Marta, 31
ID=40
Sofía, 50

Leyenda: en un índice agrupado, las hojas contienen los datos completos de la fila. En un índice no agrupado, las hojas suelen contener el valor indexado y la clave primaria.

6. Índice Clustered (Agrupado) vs Non-Clustered (No Agrupado)

Índice Clustered (Agrupado)

¿Dónde se guarda?

En InnoDB, el índice agrupado forma parte de la organización principal de la tabla. Eso significa que los datos de las filas y el índice agrupado están estrechamente ligados: las hojas del árbol son, en la práctica, las filas completas. Por eso suele explicarse que “la tabla está organizada por el índice agrupado”.

Índice Non-Clustered (No Agrupado)

¿Dónde se guarda?

El índice no agrupado se guarda en una estructura separada de la organización principal de la tabla. No contiene la fila completa, sino la clave indexada y la referencia necesaria para llegar al dato real. Por eso, cuando una consulta necesita columnas no presentes en ese índice, MySQL suele hacer una segunda navegación hacia el índice agrupado.

Importante: ¿qué pasa si no hay clave primaria?

Este punto es clave y conviene entenderlo bien. En InnoDB, el motor necesita una forma de identificar de manera única cada fila para organizar el índice agrupado.

Conclusión práctica: lo mejor es definir una PRIMARY KEY explícita. Así evitamos dejarle al motor una decisión implícita y conseguimos una organización más clara y predecible.

¿Qué diferencia hay en la lectura?

Cuando una consulta usa el índice agrupado y necesita columnas de la propia fila, muchas veces llega directamente al dato completo en una sola navegación del árbol. En cambio, cuando usa un índice no agrupado, primero encuentra la clave indexada y luego necesita una segunda búsqueda para ir al índice agrupado y obtener el resto de la fila. A esto suele explicárselo como una especie de doble salto o lookup.

Resumen ilustrativo y comparativo

Índice agrupado

Índice no agrupado

Comparación de punteros

Comparación de contenido de nodos

7. Índices compuestos o multicolumna

Un índice compuesto es un índice definido sobre dos o más columnas.

CREATE INDEX idx_apellido_nombre ON clientes (apellido, nombre);

El orden de las columnas es crítico. Si el índice está definido como (apellido, nombre), puede servir para:

Pero no sirve igual de bien para búsquedas solo por nombre, porque el árbol está ordenado primero por apellido.

¿Cómo impacta una PK compuesta en el índice agrupado?

Si definimos una clave primaria compuesta, por ejemplo (sucursal_id, cliente_id), esa combinación pasa a ser la clave que identifica físicamente las filas en el índice agrupado.

Además, los índices secundarios deben guardar esa PK compuesta en sus hojas. Eso significa que:

8. Índices únicos (UNIQUE)

Un índice único garantiza que no existan dos filas con el mismo valor en la columna o conjunto de columnas indexadas.

CREATE UNIQUE INDEX idx_email_unico ON clientes (email);

También puede aparecer al definir una restricción UNIQUE dentro de la tabla.

Además de acelerar búsquedas, los índices únicos cumplen una función de integridad: impiden duplicados no permitidos.

9. ¿Cómo utilizan los índices las consultas?

Consulta sin índice

SELECT * FROM clientes WHERE apellido = 'González';

Si no existe un índice útil sobre apellido, MySQL deberá revisar muchas filas de la tabla para encontrar coincidencias.

Consulta con índice

CREATE INDEX idx_apellido ON clientes (apellido);

SELECT * FROM clientes WHERE apellido = 'González';

Ahora MySQL puede usar el índice para localizar mucho más rápido las filas donde apellido = 'González'.

10. EXPLAIN: cómo verificar si una consulta usa índices

EXPLAIN es una herramienta fundamental para analizar cómo MySQL planea ejecutar una consulta. Antes de un SELECT, se puede anteponer EXPLAIN para observar si MySQL va a usar un índice, qué tabla lee, qué tipo de acceso utilizará y cuántas filas estima revisar.

EXPLAIN
SELECT *
FROM clientes
WHERE apellido = 'González';

¿Para qué sirve?

¿Qué campos conviene mirar?

Importante: si en EXPLAIN aparece un índice posible pero en key no se usa ninguno, eso invita a investigar. Tal vez la consulta, el orden de columnas del índice, la selectividad o el tipo de condición hacen que MySQL decida no usarlo.

Ejemplo práctico completo: antes y después de crear un índice

Este script sirve para laboratorio o práctica en clase. La idea es crear una tabla sencilla, cargar registros, ejecutar una consulta sin índice, observar EXPLAIN, luego crear el índice y volver a ejecutar exactamente la misma consulta.

Paso 1: crear una base de prueba y una tabla

CREATE DATABASE IF NOT EXISTS prueba_indices;
USE prueba_indices;

DROP TABLE IF EXISTS clientes;

CREATE TABLE clientes (
    id INT AUTO_INCREMENT PRIMARY KEY,
    apellido VARCHAR(50) NOT NULL,
    nombre VARCHAR(50) NOT NULL,
    ciudad VARCHAR(50),
    edad INT
);

Paso 2: cargar registros de ejemplo

INSERT INTO clientes (apellido, nombre, ciudad, edad) VALUES
('González', 'Ana', 'Mar del Plata', 21),
('Pérez', 'Juan', 'Batán', 19),
('Gómez', 'Lucía', 'Mar del Plata', 25),
('Rodríguez', 'Carlos', 'Miramar', 31),
('Fernández', 'Marta', 'Mar del Plata', 28),
('López', 'Sofía', 'Necochea', 24),
('González', 'Pedro', 'Balcarce', 36),
('Martínez', 'Laura', 'Mar del Plata', 22),
('González', 'Diego', 'Batán', 27),
('Sánchez', 'Paula', 'Miramar', 20),
('Díaz', 'Marina', 'Mar del Plata', 26),
('González', 'Nora', 'Necochea', 33);

Paso 3: ejecutar EXPLAIN antes de crear el índice

EXPLAIN
SELECT *
FROM clientes
WHERE apellido = 'González';
Antes de crear el índice Resultado de EXPLAIN antes de crear el índice

En esta primera imagen se ve un escenario sin un índice útil sobre apellido. La consulta puede ejecutarse, pero MySQL no dispone todavía de una estructura específica para llegar rápido a las filas buscadas.

Campo Valor antes del índice Qué significa
id1La consulta tiene una sola etapa principal de ejecución.
select_typeSIMPLEEs una consulta simple, sin subconsultas ni uniones complejas.
tableclientesLa tabla analizada en este paso es clientes.
partitionsNULLLa tabla no está particionada o no intervienen particiones en este caso.
typeALLEs uno de los peores tipos de acceso en términos de eficiencia. Significa recorrido completo de la tabla.
possible_keysNULLMySQL no ve ningún índice disponible que pueda ayudar en esta consulta.
keyNULLNo se usa ningún índice porque no existe uno útil para esta condición.
key_lenNULLComo no se usa índice, no hay longitud de clave aplicada.
refNULLNo hay comparación contra un índice.
rows12MySQL estima que debe revisar 12 filas. En esta tabla son pocas, pero en una tabla grande el impacto sería mucho mayor.
filtered10.00Estimación de qué porcentaje de filas revisadas pasará el filtro. Aquí el optimizador estima que solo una parte pequeña de lo recorrido servirá.
ExtraUsing whereMySQL aplica la condición WHERE para filtrar filas después de leerlas.

Paso 4: crear el índice B-Tree sobre la columna buscada

CREATE INDEX idx_clientes_apellido
ON clientes (apellido);

En MySQL, salvo indicación contraria y según el motor, este índice será de tipo B-Tree, adecuado para búsquedas exactas como apellido = 'González'.

Paso 5: repetir exactamente la misma consulta con EXPLAIN

EXPLAIN
SELECT *
FROM clientes
WHERE apellido = 'González';
Después de crear el índice Resultado de EXPLAIN después de crear el índice

Ahora sí existe un índice sobre apellido y MySQL puede aprovecharlo. Se ve claramente que el plan de ejecución cambió.

Campo Valor después del índice Qué significa
id1La consulta sigue siendo una única etapa principal.
select_typeSIMPLELa forma general de la consulta no cambió.
tableclientesLa tabla sigue siendo la misma.
partitionsNULLNo hay particiones involucradas.
typerefEste tipo de acceso es mucho mejor que ALL. Indica que MySQL usa un índice no único para encontrar filas que coinciden con un valor de búsqueda.
possible_keysidx_clientes_apellidoAhora sí existe un índice que podría ayudar a resolver la consulta.
keyidx_clientes_apellidoMySQL eligió efectivamente ese índice.
key_len202MySQL informa la longitud de clave que está utilizando. Este dato depende del tipo de columna, la codificación y cómo el motor representa internamente el acceso.
refconstLa comparación del índice se hace contra un valor constante, en este caso el literal 'González'.
rows4Ahora estima revisar solo 4 filas, es decir, las filas candidatas encontradas por el índice, en lugar de recorrer toda la tabla.
filtered100.00De las filas candidatas encontradas por el índice, el optimizador estima que todas cumplen el filtro. Tiene sentido porque el índice ya llevó directamente a los apellidos buscados.
ExtraNULLNo hace falta informar un filtrado adicional destacado como en el caso anterior; el acceso ya quedó mucho más afinado gracias al índice.

Comparación directa: antes vs después

Campo Antes Después Lectura didáctica
typeALLrefSe pasó de un recorrido completo de tabla a un acceso por índice.
possible_keysNULLidx_clientes_apellidoAntes no había un índice candidato; después sí.
keyNULLidx_clientes_apellidoAntes no se usaba índice; después MySQL usa el creado.
rows124La estimación de filas a revisar bajó de manera clara.
refNULLconstDespués del índice, la búsqueda se apoya en una comparación directa contra un valor constante.
ExtraUsing whereNULLEn el primer caso el filtrado se hace luego de leer toda la tabla; en el segundo, el acceso ya está mucho más restringido gracias al índice.
Importante: EXPLAIN no devuelve los datos finales de la consulta. Lo que devuelve es el plan de ejecución estimado, es decir, cómo piensa trabajar MySQL para obtener esos datos.

Variación útil: consulta con ORDER BY

EXPLAIN
SELECT id, apellido, nombre
FROM clientes
WHERE apellido = 'González'
ORDER BY nombre;

En este caso, el índice sobre apellido puede ayudar al filtrado, pero no necesariamente resolver por sí solo el ordenamiento por nombre. Aquí aparece la utilidad de los índices compuestos.

Variación útil: índice compuesto

CREATE INDEX idx_clientes_apellido_nombre
ON clientes (apellido, nombre);

EXPLAIN
SELECT id, apellido, nombre
FROM clientes
WHERE apellido = 'González'
ORDER BY nombre;

Esta prueba permite ver por qué el orden de las columnas dentro del índice compuesto importa tanto. Si primero filtramos por apellido y después ordenamos por nombre, un índice (apellido, nombre) puede resultar muy conveniente.

11. Índices Full-Text (texto completo)

Los índices Full-Text están diseñados para búsquedas en grandes bloques de texto. No son simplemente una versión rápida de LIKE '%palabra%'; trabajan de otra manera.

¿Cómo funcionan?

En lugar de buscar coincidencias carácter por carácter en todo el texto, el motor tokeniza el contenido, es decir, lo divide en palabras o unidades llamadas tokens. Luego las almacena en una estructura que permite búsquedas por relevancia.

Tokenización: proceso de dividir el texto en tokens, por lo general palabras individuales, descartando elementos poco útiles para la búsqueda.

¿Qué es un token?

Un token es una unidad elemental de información. En un índice Full-Text, normalmente un token es una palabra relevante extraída del texto.

Proceso de tokenización

  1. Extracción de palabras: se toma el contenido de una columna de texto y se lo separa en palabras o términos.
  2. Eliminación de palabras irrelevantes: se descartan muchas palabras comunes o stop words, como “y”, “de”, “la”, “el”, según configuración y motor.
  3. Indexación de tokens: se guardan los tokens junto con información que permite saber en qué filas aparecen.

¿Dónde se guarda?

Didácticamente puede explicarse como una estructura interna asociada a la tabla que relaciona tokens con filas. No es la misma organización que un B-Tree clásico de igualdad o rango. Lo importante es entender que el motor mantiene un índice especializado para palabras y referencias a los registros donde aparecen.

Ejemplo de idea conceptual

Si una fila contiene el texto “Los avances en la inteligencia artificial son sorprendentes”, el sistema puede quedarse con tokens como avances, inteligencia, artificial y sorprendentes, descartando otras palabras comunes.

Ejemplo de creación y uso

CREATE TABLE articulos (
    id INT PRIMARY KEY,
    titulo VARCHAR(200),
    contenido TEXT,
    FULLTEXT(titulo, contenido)
);

SELECT *
FROM articulos
WHERE MATCH(titulo, contenido) AGAINST('base de datos');

También puede crearse con una instrucción separada:

CREATE FULLTEXT INDEX idx_contenido ON articulos (contenido);

Ventajas

Limitaciones

Optimización de búsquedas Full-Text

12. Índices Hash

¿Qué son?

Un índice Hash utiliza una función hash para convertir un valor en un identificador y localizar el dato asociado de forma directa. No organiza los datos por orden, sino por el resultado del hash.

¿Cómo funcionan?

¿Dónde se guardan?

En el caso típico de tablas MEMORY, los índices y los datos se mantienen en memoria RAM mientras la tabla exista y el servidor permanezca activo. Esto explica su gran velocidad y también una de sus limitaciones más importantes: su comportamiento está pensado para datos temporales o altamente volátiles, no para persistencia clásica.

Fortaleza principal

Son extremadamente rápidos para búsquedas de igualdad, como = o IN.

Debilidad principal

No tienen orden lógico. Por eso no sirven para:

Ejemplo

CREATE TABLE productos (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nombre VARCHAR(255),
    precio DECIMAL(10, 2),
    tipo_memoria VARCHAR(50)
) ENGINE=MEMORY;

CREATE INDEX idx_tipo_hash ON productos (tipo_memoria) USING HASH;

SELECT * FROM productos WHERE tipo_memoria = 'SSD';

Ventajas

Limitaciones

¿Qué es una colisión?

Una colisión ocurre cuando dos valores distintos producen el mismo hash. El motor puede resolverlo, pero si hay muchas colisiones el rendimiento puede deteriorarse.

¿Cuándo usar índices Hash?

13. El motor MEMORY: cuándo podría usarse

El motor MEMORY almacena sus datos en RAM. Eso lo hace muy rápido, pero también implica limitaciones importantes. Didácticamente conviene pensar que no es el motor común para una base transaccional persistente, sino una herramienta para casos muy puntuales.

Casos en los que podría resultar útil

Cuándo no conviene

Importante: MEMORY puede ser excelente para ejemplos, pruebas y estructuras temporales, pero no debe elegirse solo porque “es más rápido”. Siempre hay que preguntarse si los datos deben persistir y qué tipo de consultas se harán.

14. Mantenimiento de índices

Los índices también requieren mantenimiento. Con el tiempo, inserciones, actualizaciones y eliminaciones pueden afectar su organización y el rendimiento general.

Optimizar índices

OPTIMIZE TABLE nombre_tabla;

Esta operación puede ayudar a reorganizar y optimizar la estructura asociada a la tabla y sus índices, dependiendo del motor.

Modificar un índice

MySQL no permite, en general, cambiar directamente las columnas de un índice existente con una instrucción tipo “editar índice”. Lo habitual es:

  1. Eliminar el índice viejo.
  2. Crear uno nuevo con la estructura deseada.
DROP INDEX nombre_indice ON nombre_tabla;

CREATE INDEX nuevo_nombre_indice
ON nombre_tabla (nueva_columna1, nueva_columna2);

Eliminar un índice

DROP INDEX nombre_indice ON nombre_tabla;

15. Buenas prácticas

Sobre las FOREIGN KEY

Conviene recordar que una FK sin buen soporte de indexación puede volver lentos los JOIN y ciertas validaciones. En la práctica, siempre es recomendable revisar cómo quedaron indexadas las columnas implicadas en relaciones.

16. Preguntas guía con respuesta

1. ¿Por qué los B-Tree sirven de base para muchos índices?
Porque ofrecen una estructura versátil: soportan igualdad, rangos y ordenamiento. Por eso muchos motores los usan como base para gran parte de sus índices generales.

2. ¿Qué sucede al declarar una PK?
En InnoDB, normalmente esa PK pasa a ser el índice agrupado de la tabla.

3. ¿Qué pasa si no declaramos PK?
El motor necesita igualmente una forma de identificar filas. Puede usar una clave UNIQUE NOT NULL adecuada y, si tampoco existe, crear una clave interna oculta comparable a un rowid.

4. ¿Qué diferencia hay para el motor al leer usando un clustered o un non-clustered?
Con el clustered suele poder llegar más directo a la fila completa. Con el non-clustered primero encuentra la referencia y luego debe buscar la fila real en el índice agrupado.

5. ¿Por qué un índice no agrupado apunta al agrupado?
Porque el agrupado representa la organización principal de las filas. Así el índice secundario no necesita duplicar todos los datos completos de la fila.

6. ¿Qué impacto tiene que el motor termine usando un rowid oculto?
Se pierde claridad de diseño y dejamos una decisión importante en manos del motor. Por eso es mucho mejor definir una PK explícita y estable.

7. ¿Cómo afectan los índices a las FK?
Un buen indexado sobre columnas relacionadas ayuda enormemente al rendimiento de JOIN, búsquedas y controles asociados a integridad referencial.

8. ¿Cómo impacta una PK compuesta?
Afecta el orden físico del índice agrupado y también el tamaño de índices secundarios, porque estos deben almacenar esa PK compuesta como referencia.

17. Cierre

La idea central es simple: los índices aceleran lecturas, pero tienen costo de mantenimiento. Por eso deben diseñarse en función de consultas reales. Entender la diferencia entre buscar datos, ordenar datos y mantener datos ayuda a pensar la base de datos con criterio y no solo desde la sintaxis.