Índices: estructura interna y costo
Un índice es un árbol B+ ordenado que evita leer toda la tabla. Entender su forma explica por qué sirve para un rango pero no para un LIKE con comodín adelante, y por qué cada índice hace más lenta la escritura.
Para este tema conviene tener claro:Tries y árboles BSELECT, filtros y orden
“La consulta está lenta, ponele un índice” funciona lo bastante seguido como para volverse reflejo. Pero un índice no es magia: es una estructura concreta, con una forma concreta, y esa forma decide qué consultas acelera, cuáles no toca y cuánto cuesta mantenerla.
El árbol B+, que es lo que hay adentro
La estructura dominante es el árbol B+: un árbol balanceado de altura muy baja, donde cada nodo ocupa una página de disco y tiene cientos de hijos. Con tres o cuatro niveles se indexan millones de filas.
Los valores viven en las hojas, ordenados, y las hojas están enlazadas entre sí. Buscar un valor es bajar tres o cuatro páginas; recorrer un rango es bajar una vez y después seguir la lista de hojas. Esas dos propiedades explican casi todo lo que sigue.
Un árbol B+ chiquito. Los nodos de arriba sólo guían; los valores de verdad están abajo.
Antes de seguir, predecí
Qué acelera un índice, además de buscar
Como el índice está ordenado, sirve para igualdad, para rangos, para ORDER BY por esa columna y
para MIN y MAX, que son leer el primer o el último valor.
Y no sirve cuando el orden no ayuda: LIKE '%texto' con comodín adelante no tiene prefijo por
donde bajar, y una función aplicada a la columna —YEAR(fecha)— produce un valor que no está
indexado. En ese caso la salida es indexar la expresión, si el motor lo permite.
Índices sobre varias columnas y el orden que importa
Un índice sobre varias columnas se ordena por la primera, y dentro de cada valor por la segunda: como un directorio ordenado por apellido y después por nombre.
De ahí la regla del prefijo: un índice (a, b, c) sirve para consultas por a, por a y b, y
por las tres; no sirve para una consulta sólo por b. Y el orden de las columnas importa: las
de igualdad van primero, las de rango después, porque una vez que se abre un rango el resto del
orden deja de servir para filtrar.
El índice cubriente responde sin tocar la tabla
Si un índice contiene todas las columnas que la consulta necesita, el motor responde sin tocar la tabla. Se llama índice cubriente y suele ser la diferencia entre rápido y muy rápido, porque evita un salto a la tabla por cada fila encontrada.
Ese salto es también el motivo por el que a veces el motor ignora un índice que existe: si la consulta va a traer una fracción grande de la tabla, leerla entera de forma secuencial sale más barato que hacer miles de saltos aleatorios. No es un error del optimizador.
Lo que cuesta cada índice
Los índices no son gratis. Cada INSERT, UPDATE o DELETE tiene que actualizar todos los
índices afectados, así que cada índice extra hace más lenta toda escritura y ocupa disco y memoria
caché.
Por eso la pregunta útil no es “¿qué índice falta?” sino “¿qué índices sobran?”. Índices duplicados, prefijos de otros ya existentes, o creados para una consulta que ya no se usa, son costo puro. Los motores llevan estadísticas de uso y conviene mirarlas antes de agregar el siguiente.
Otras estructuras para otros problemas
Además del árbol B+ hay otras estructuras para otros problemas. El índice hash sirve sólo para igualdad y no para rangos. Los índices de texto completo invierten el texto en una lista de términos. Los espaciales y los de tipo GiST manejan geometrías y contenciones.
Y dos variantes muy rentables: el índice parcial, que indexa sólo las filas que cumplen una condición —los pedidos pendientes, que son el 1% de la tabla—, y el índice único, que además de acelerar declara una invariante.
El índice compuesto y el orden de sus columnas
CREATE INDEX ON orders (customer_id, created_at);
-- Usa el índice entero: filtra por la primera y ordena por la segunda
SELECT * FROM orders
WHERE customer_id = 42
ORDER BY created_at DESC
LIMIT 20;
-- Usa sólo el prefijo: filtra por customer_id y después ordena en memoria
SELECT * FROM orders WHERE customer_id = 42 ORDER BY total DESC;
-- No lo usa: created_at no es prefijo del índice
SELECT * FROM orders WHERE created_at >= '2026-01-01';
-- Índice que cubre: el motor contesta sin tocar la tabla
CREATE INDEX ON orders (customer_id, created_at) INCLUDE (total);Un índice compuesto es como una guía telefónica ordenada por apellido y después por nombre: sirve para buscar por apellido, y para buscar sólo por nombre no sirve para nada. Por eso el orden de las columnas no es un detalle: define qué consultas puede resolver.
La regla práctica para elegir el orden es poner primero las columnas que se filtran por igualdad y después las que se usan para rangos o para ordenar. Y el índice que cubre —que incluye todas las columnas pedidas— evita el salto a la tabla, que es el acceso más caro de la consulta.
Cuándo un índice no ayuda
| Situación | ¿Sirve el índice? | Por qué |
|---|---|---|
| La consulta devuelve el 40 % de la tabla | no | recorrer todo es más barato que ir y venir al índice |
| La columna tiene dos valores distintos | casi nunca | muy baja selectividad |
| La tabla tiene mil filas | da igual | entra entera en memoria |
| Se escribe mucho más de lo que se lee | hay que medir | el costo está en mantenerlo |
| El filtro usa una función sobre la columna | no, salvo índice de expresión | el valor calculado no está indexado |
Cierre
Un árbol B+ ordenado y de poca altura: por eso sirve para igualdad, rangos y orden, y no para un comodín adelante ni para la columna envuelta en una función. En los compuestos manda el prefijo, un índice cubriente evita tocar la tabla, y cada índice de más se paga en cada escritura.
Autoevaluación
¿Lo entendiste?
Práctica