Índices
Un índice es una estructura de acceso auxiliar que permite encontrar filas sin escanear la tabla completa. Funciona como el índice de un libro: vas directo a la página sin leer todo.
Sin índice vs con índice
La base de datos no sabe dónde está el registro que cumple la condición. Por eso recorre la tabla secuencialmente: lee fila a fila, evalúa la condición y sigue hasta terminar.
-- Sin índice: la BD no sabe dónde está el email → recorre TODAS las filas (Seq Scan)
SELECT * FROM usuarios WHERE email = 'ana@ejemplo.com';
-- Con índice: la BD salta directo a la fila (Index Scan)
CREATE INDEX idx_usuarios_email ON usuarios (email);
| Escenario | Seq Scan | Index Scan |
|---|---|---|
| Tabla chica (< 10k filas) | Rápido | Overhead innecesario |
| Tabla grande, filtro selectivo | Lento | Rápido |
| Tabla grande, filtro no selectivo | A veces mejor | Peor (lee índice + tabla) |
Selectividad
La selectividad mide qué tan bien un filtro reduce la cantidad de filas. Un campo con alta selectividad devuelve muy pocas filas (idealmente una); uno con baja selectividad devuelve muchas.
-- Alta selectividad → el índice sirve
WHERE email = 'ana@ejemplo.com' -- único, busca 1 entre millones
WHERE dni = '12345678' -- clave única
WHERE id_pedido = 4521 -- PK / único
-- Baja selectividad → el índice NO sirve
WHERE activo = true -- la mitad de la tabla
WHERE genero = 'M' -- ~50%
WHERE creado_anio = 2024 -- un año entero de datos
WHERE estado IN ('activo', 'pendiente') -- la mayoría
Si el filtro no descarta casi nada, el optimizador prefiere Seq Scan: leer el índice y luego saltar a la tabla (heap) cuesta más que escanear directo.
B-Tree (el estándar)
Mantiene los valores ordenados y organizados en niveles para descartar grandes grupos de datos rápidamente.
La mayoría de motores usan B+Tree por defecto.
Trade-off
| Operación | Con índice | Sin índice |
|---|---|---|
INSERT |
+1-2 escrituras (heap + índice) | 1 escritura |
UPDATE (columna indexada) |
+1-2 escrituras | 1 escritura |
DELETE |
+1 escritura (marcar en índice) | 1 escritura |
SELECT (filtrado) |
O(log N) | O(N) |
Regla práctica: indexa columnas que filtras en
WHERE,JOIN,ORDER BY. Mide conEXPLAIN ANALYZE. Elimina índices no usados (pg_stat_user_indexes).
Índices compuestos (multi-columna)
-- Orden importa: izquierda a derecha
CREATE INDEX idx_pedidos_cliente_fecha ON pedidos (cliente_id, fecha);
| Query | Usa índice? |
|---|---|
WHERE cliente_id = 5 |
✅ |
WHERE cliente_id = 5 AND fecha > '2024-01-01' |
✅ |
WHERE fecha > '2024-01-01' |
❌ (no empieza por cliente_id) |