Índice compuesto

Un índice compuesto (o multicolumna) incluye dos o más columnas en un solo índice. MySQL puede usarlo cuando la consulta filtra, ordena o agrupa por las columnas incluidas, siguiendo el orden en que fueron definidas. La regla más importante es la del prefijo izquierdo: MySQL solo puede usar el índice desde la columna más a la izquierda en adelante.

Sintaxis

CREATE INDEX nombre_indice ON tabla (columna1, columna2, columna3);

Ejemplo básico

-- Crear índice compuesto en pedidos
CREATE INDEX idx_pedidos_estado_fecha ON pedidos (estado, fecha_pedido);
 
-- Esta consulta usa las dos columnas del índice
EXPLAIN SELECT * FROM pedidos
WHERE estado = 'pendiente' AND fecha_pedido > '2025-12-01';
idselect_typetabletypepossible_keyskeyrowsExtra
1SIMPLEpedidosrangeidx_pedidos_estado_fechaidx_pedidos_estado_fecha5Using index condition

MySQL usa el índice compuesto para filtrar por estado y luego por fecha dentro de ese estado.

Regla del prefijo izquierdo

MySQL solo puede usar las columnas del índice de izquierda a derecha. Para un índice (A, B, C):

ConsultaUsa el índice
WHERE A = 1Sí (usa A)
WHERE A = 1 AND B = 2Sí (usa A, B)
WHERE A = 1 AND B = 2 AND C = 3Sí (usa A, B, C)
WHERE B = 2No (falta A)
WHERE C = 3No (faltan A y B)
WHERE A = 1 AND C = 3Parcial (usa solo A)
WHERE B = 2 AND C = 3No (falta A)
-- Usa el índice (prefijo izquierdo: estado)
EXPLAIN SELECT * FROM pedidos WHERE estado = 'enviado';
idselect_typetabletypepossible_keyskeyrowsExtra
1SIMPLEpedidosrefidx_pedidos_estado_fechaidx_pedidos_estado_fecha3NULL
-- NO usa el índice (no empieza por la columna izquierda)
EXPLAIN SELECT * FROM pedidos WHERE fecha_pedido > '2025-12-01';
idselect_typetabletypepossible_keyskeyrowsExtra
1SIMPLEpedidosALLNULLNULL25Using where

type: ALL confirma el full table scan. La consulta necesita su propio índice sobre fecha_pedido.

Orden de columnas

El orden de las columnas en el índice importa. La regla general es:

  1. Columnas de igualdad primero (=): proporcionan el filtro más estrecho
  2. Columnas de rango después (>, <, BETWEEN): el rango "rompe" el uso del índice para las columnas siguientes
-- Índice para: estado = ? AND fecha_pedido BETWEEN ? AND ?
-- Orden correcto: (estado, fecha_pedido)
-- Incorrecto: (fecha_pedido, estado) - el rango en fecha impide usar estado
 
CREATE INDEX idx_cliente_estado ON pedidos (cliente_id, estado);
 
-- Usa ambas columnas
EXPLAIN SELECT * FROM pedidos
WHERE cliente_id = 1 AND estado = 'entregado';
idselect_typetabletypepossible_keyskeyrowsExtra
1SIMPLEpedidosrefidx_cliente_estado,cliente_ididx_cliente_estado2NULL

Índice compuesto vs múltiples índices

MySQL generalmente solo usa un índice por tabla por consulta (con excepciones como index merge). Un índice compuesto bien diseñado suele ser mejor que varios índices individuales:

-- Dos índices separados
CREATE INDEX idx_a ON pedidos (estado);
CREATE INDEX idx_b ON pedidos (fecha_pedido);
 
-- Un índice compuesto (generalmente mejor)
CREATE INDEX idx_ab ON pedidos (estado, fecha_pedido);
EstrategiaPara WHERE estado = ? AND fecha_pedido > ?
Solo idx_aFiltra por estado, luego escanea por fecha
Solo idx_bFiltra por fecha, luego escanea por estado
idx_ab compuestoFiltra por ambos eficientemente

Índice compuesto para ORDER BY

-- Índice que cubre filtro y ordenamiento
CREATE INDEX idx_estado_fecha ON pedidos (estado, fecha_pedido);
 
-- El índice cubre el WHERE y el ORDER BY
EXPLAIN SELECT id, fecha_pedido, total
FROM pedidos
WHERE estado = 'pendiente'
ORDER BY fecha_pedido DESC;
idselect_typetabletypepossible_keyskeyrowsExtra
1SIMPLEpedidosrefidx_estado_fechaidx_estado_fecha7Backward index scan

Backward index scan indica que MySQL recorre el índice en orden inverso para resolver el ORDER BY DESC sin un paso de ordenamiento adicional.

Ejemplo práctico: consulta de productos

-- Consulta frecuente: productos activos de una categoría, ordenados por precio
CREATE INDEX idx_prod_cat_activo_precio ON productos (categoria_id, activo, precio);
 
EXPLAIN SELECT nombre, precio
FROM productos
WHERE categoria_id = 6 AND activo = TRUE
ORDER BY precio;
idselect_typetabletypepossible_keyskeyrowsExtra
1SIMPLEproductosrefidx_prod_cat_activo_precioidx_prod_cat_activo_precio4Using index condition

El índice filtra por categoría, luego por activo, y los resultados ya salen ordenados por precio.

Cuántas columnas incluir

No hay un límite práctico estricto, pero:

ColumnasRecomendación
2-3Ideal para la mayoría de consultas
4-5Aceptable si la consulta lo justifica
6+Rara vez necesario, puede indicar un problema de diseño

Cada columna adicional aumenta el tamaño del índice y ralentiza las escrituras.

Limpieza

DROP INDEX idx_pedidos_estado_fecha ON pedidos;
DROP INDEX idx_cliente_estado ON pedidos;
DROP INDEX idx_a ON pedidos;
DROP INDEX idx_b ON pedidos;
DROP INDEX idx_ab ON pedidos;
DROP INDEX idx_estado_fecha ON pedidos;
DROP INDEX idx_prod_cat_activo_precio ON productos;

En el siguiente artículo veremos los índices únicos, que combinan la velocidad de búsqueda con la garantía de no duplicados.

Escrito por Eduardo Lázaro