Databases 10 de enero, 2026 • 10 min de lectura

PostgreSQL: Índices que realmente importan

Aprende a crear índices eficientes en PostgreSQL que mejoren el rendimiento de tus consultas sin desperdiciar recursos.

¿Por qué los índices son importantes?

Un índice mal diseñado puede ser peor que no tener índice. PostgreSQL ofrece varios tipos de índices, y elegir el correcto puede reducir el tiempo de consulta de minutos a milisegundos.

1. Índice B-Tree (por defecto)

El índice más común y versátil. Ideal para comparaciones de igualdad y rangos:

-- Crear índice simple
CREATE INDEX idx_users_email ON users(email);

-- Índice compuesto (orden importa)
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at DESC);

-- Cuándo usarlo:
SELECT * FROM users WHERE email = 'user@example.com';
SELECT * FROM orders WHERE user_id = 123 ORDER BY created_at DESC;

2. Índice Hash

Más rápido que B-Tree para búsquedas exactas, pero solo soporta igualdad (=):

CREATE INDEX idx_sessions_token ON sessions USING HASH(token);

-- Perfecto para:
SELECT * FROM sessions WHERE token = 'abc123xyz';

3. Índice GIN (Generalized Inverted Index)

Esencial para búsquedas de texto completo, arrays y JSONB:

-- Para búsqueda de texto
CREATE INDEX idx_articles_content ON articles USING GIN(to_tsvector('spanish', content));

-- Para JSONB
CREATE INDEX idx_products_metadata ON products USING GIN(metadata);

-- Para arrays
CREATE INDEX idx_posts_tags ON posts USING GIN(tags);

-- Consultas optimizadas:
SELECT * FROM articles WHERE to_tsvector('spanish', content) @@ to_tsquery('postgresql');
SELECT * FROM products WHERE metadata @> '{"category": "electronics"}';
SELECT * FROM posts WHERE tags @> ARRAY['postgresql', 'performance'];

4. Índice Parcial

Indexa solo un subconjunto de filas, ahorrando espacio y mejorando velocidad:

-- Solo indexar usuarios activos
CREATE INDEX idx_users_active_email ON users(email) WHERE status = 'active';

-- Solo pedidos pendientes
CREATE INDEX idx_orders_pending ON orders(created_at) WHERE status = 'pending';

-- Ventaja: índice más pequeño y rápido

5. Índice de Expresión

Indexa el resultado de una función o expresión:

-- Búsqueda case-insensitive
CREATE INDEX idx_users_lower_email ON users(LOWER(email));

-- Búsqueda por fecha sin hora
CREATE INDEX idx_logs_date ON logs(DATE(created_at));

-- Uso:
SELECT * FROM users WHERE LOWER(email) = 'user@example.com';
SELECT * FROM logs WHERE DATE(created_at) = '2026-01-15';

6. Índice BRIN (Block Range Index)

Extremadamente eficiente para tablas grandes con datos ordenados naturalmente (logs, series temporales):

-- Ideal para logs con timestamps
CREATE INDEX idx_logs_created_at ON logs USING BRIN(created_at);

-- Ventaja: índice de 1MB vs 100MB con B-Tree
-- Perfecto para:
SELECT * FROM logs WHERE created_at BETWEEN '2026-01-01' AND '2026-01-31';

Herramientas de análisis

Usa estas queries para identificar índices faltantes o innecesarios:

-- Ver plan de ejecución
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'test@example.com';

-- Índices no utilizados
SELECT schemaname, tablename, indexname, idx_scan
FROM pg_stat_user_indexes
WHERE idx_scan = 0 AND indexrelname NOT LIKE 'pg_toast%';

-- Tamaño de índices
SELECT indexrelname, pg_size_pretty(pg_relation_size(indexrelid))
FROM pg_stat_user_indexes
ORDER BY pg_relation_size(indexrelid) DESC;

Mejores prácticas

  • No indexes todas las columnas: cada índice consume espacio y ralentiza INSERT/UPDATE
  • Usa índices compuestos para consultas con múltiples WHERE
  • El orden en índices compuestos importa: columnas más selectivas primero
  • Ejecuta VACUUM ANALYZE regularmente
  • Monitorea con pg_stat_statements

⚡ Regla de oro:

Un índice debe usarse al menos 100 veces más de lo que se actualiza. Si una tabla tiene muchos INSERTS/UPDATES, limita los índices a lo esencial.