Guía definitiva: Índices y rendimiento en SQL para desarrolladores
Los índices son la herramienta más potente (y a la vez más mal utilizada) para acelerar consultas en bases de datos relacionales. Esta guía concentra los conceptos esenciales, ejemplos prácticos y errores comunes para que puedas aplicar índices de forma efectiva sin degradar el rendimiento.
1. ¿Por qué importan los índices?
- Permiten localizar filas sin escanear tablas completas (evitan full table scans).
- Reducen I/O y CPU en lecturas frecuentes; pueden acelerar JOINs y ORDER BY.
- Pero añaden coste en inserciones/actualizaciones/borrados y consumo adicional de espacio.
2. Cómo funcionan (breve)
- B-tree: estructura jerárquica balanceada, la más común para búsqueda por rango y igualdad.
- Hash: búsquedas por igualdad muy rápidas, no sirve para rangos (ej. algunos motores).
- Otros: GiST/GIN (Postgres) para datos geométricos o jsonb/arrays; BRIN para tablas muy grandes y append-only.
3. Tipos de índices y cuándo usarlos
- Clustered (SQL Server / InnoDB): determina el orden físico de la tabla. Útil en rangos por la columna cluster.
- Non-clustered: índice separado que apunta a filas; ideal para búsquedas frecuentes.
- Unique: garantiza unicidad además de acelerar consultas.
- Partial/filtered (Postgres/SQL Server): indexa solo filas que cumplen una condición; reduce tamaño y mejora selectividad.
- Expression / function-based: indexa el resultado de una expresión (ej. lower(email)).
- Covering index: índice que contiene todas las columnas necesarias para la consulta, evitando acceder a la tabla base.
4. Principios clave
- Selectividad: cuanto más selectiva (pocos valores por clave) sea la columna, más beneficio.
- Orden en índices compuestos: el orden importa. "(a, b)" sirve para búsquedas por a y por (a,b), pero no eficientemente para búsquedas solo por b.
- Sargability: evita envolver columnas en funciones (col = 'x' es sargable, LOWER(col) = 'x' no lo es a menos que haya un índice sobre LOWER(col)).
- Evita sobre-indexar: muchas escrituras + muchos índices = latencia y trabajo de mantenimiento.
5. Ejemplo práctico (Postgres) — tabla y problemas comunes
Supongamos una tabla de pedidos:
CREATE TABLE orders (
id bigserial PRIMARY KEY,
customer_id bigint NOT NULL,
status varchar(20),
created_at timestamptz NOT NULL DEFAULT now(),
total numeric(10,2)
);
Consulta común lenta:
SELECT * FROM orders
WHERE customer_id = 42
ORDER BY created_at DESC
LIMIT 20;
Sin índice, Postgres hará un seq scan. Medimos con:
EXPLAIN ANALYZE
SELECT * FROM orders
WHERE customer_id = 42
ORDER BY created_at DESC
LIMIT 20;
Solución: un índice compuesto que sirva para la condición y el ORDER BY:
CREATE INDEX idx_orders_customer_createdat
ON orders (customer_id, created_at DESC);
Resultados:
- El plan ideal es un index scan usando el índice compuesto y un Top-N (limit).
- Si además solo necesitas algunas columnas, hacer un índice covering (o una proyección) evita tocar la tabla.
6. Variantes según motor
- Postgres: soporta índices parciales, expression indexes, GIN/GiST para jsonb, arrays, full-text.
- MySQL (InnoDB): índices compuestos, prefijo de índice para strings, cubrir mediante orden de columnas; no tiene INCLUDE hasta versiones recientes (MySQL 8.0.13 introdujo optimizer hints y mejoras, pero INCLUDE es de SQL Server/Postgres tiene INCLUDE?).
- SQL Server: INCLUDE permite columnas no clave en el índice (covering efficient).
7. Estadísticas y mantenimiento
- Postgres: RUN ANALYZE (o VACUUM ANALYZE) para actualizar estadísticas y que el optimizador elija buenos planes.
- MySQL: ANALYZE TABLE, OPTIMIZE TABLE; revisa el slow query log.
- Reindexar periódicamente si hay bloat: REINDEX (Postgres) o ALTER INDEX ... REBUILD (SQL Server).
8. Cómo medir impacto
- EXPLAIN / EXPLAIN ANALYZE / EXPLAIN (FORMAT JSON) para ver costes y operaciones.
- pg_stat_user_indexes y pg_stat_user_tables para Postgres: ver usage y scans por índice.
- Comparar latencias reales con cargas de prueba (staging) — los microbenchmarks no sustituyen pruebas con datos reales.
9. Errores comunes (y cómo evitarlos)
- Indexar absolutamente todo: crea sobrecarga en escrituras y aumenta I/O. Regla: indexa columnas usadas frecuentemente en WHERE, JOIN, ORDER BY.
- Orden incorrecto en índices compuestos: piensa en los predicados más selectivos y en cómo se consultan las columnas.
- Ignorar funciones: si consultas LOWER(email) mucho, crea índice sobre LOWER(email) en lugar de aplicar la función en cada fila.
- Confiar solo en índices para resolver diseño: índices no arreglan esquemas con joins enormes y mala normalización.
- No mantener estadísticas: el optimizador elige mal sin estadísticas recientes.
10. Checklist práctico antes de crear un índice
- ¿La columna aparece en WHERE, JOIN, ORDER BY o SELECT (para covering)?
- ¿Cuál es la selectividad esperada?
- ¿La consulta se beneficiará de rango o solo de igualdad?
- ¿Cuál es el coste de mantenimiento (tu tasa de writes)?
- Probar en staging: medir EXPLAIN ANALYZE antes y después.
11. Ejemplos adicionales útiles
Índice parcial en Postgres:
CREATE INDEX idx_orders_active_customer
ON orders (customer_id)
WHERE status = 'active';
Índice sobre función (Postgres):
CREATE INDEX idx_users_lower_email ON users (lower(email));
-- luego consultas: WHERE lower(email) = 'a@b.com' serán sargables
Índice covering en SQL Server:
CREATE NONCLUSTERED INDEX idx_orders_customer_include_total
ON orders (customer_id)
INCLUDE (total, created_at);
12. Rendimiento en escrituras y estrategias para cambiar índices en producción
- Crear índices incrementales en ventanas de baja actividad.
- Usar herramientas online: pg_repack (Postgres), pt-online-schema-change (Percona) o ALTER INDEX REBUILD ONLINE (SQL Server).
- Evitar reconstrucciones masivas en picos de carga.
13. Diagnóstico rápido: ¿por qué una consulta sigue lenta?
- Revisar plan con EXPLAIN ANALYZE.
- Confirmar estadísticas actualizadas (ANALYZE).
- Ver si la consulta es sargable o si hay funciones en las columnas filtradas.
- Comprobar si existe un índice con el orden correcto o si necesitas un índice compuesto/partial.
- Medir IO y latencia: ¿es CPU-bound o IO-bound?
14. Acciones recomendadas (rápidas)
- Empieza por índices compuestos que cubran WHERE + ORDER BY + JOIN.
- Usa índices parciales para estados poco frecuentes (status = 'active').
- Implementa índices sobre expresiones si usas funciones en predicados.
- Monitorea uso de índices y elimina los que no se usan.
Consejo avanzado: para tablas enormes con inserciones secuenciales y consultas por rangos temporales, considera BRIN en Postgres (muy pequeño y eficiente para datos físicamente ordenados). Advertencia: antes de aplicar cambios en producción, prueba en staging con datos representativos y crea índices en ventanas controladas o mediante herramientas online para evitar bloqueos largos.
Siguiente paso recomendado: identifica 3 consultas críticas en tu aplicación, ejecuta EXPLAIN ANALYZE, aplica un índice temporal en staging, vuelve a medir y documenta el cambio de latencia y coste en I/O.
¿Quieres comentar?
Inicia sesión con Telegram para participar en la conversación