Saltar para o conteúdo
PostgreSQL: Índices que Fazem Diferença na Performance
Banco de Dados

PostgreSQL: Índices que Fazem Diferença na Performance

10 de maio de 2026·Paulo Pereira

Por que Índices Importam?

Sem índice, o PostgreSQL faz sequential scan — lê todas as linhas da tabela. Com 1 milhão de registros, uma query de 100ms vira 10 segundos. O índice certo transforma isso de volta em milissegundos.

EXPLAIN ANALYZE: Seu Melhor Amigo

Antes de criar qualquer índice, use EXPLAIN ANALYZE para entender o plano de execução:

EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT * FROM pedidos
WHERE usuario_id = 123
  AND status = 'pendente'
  AND criado_em > NOW() - INTERVAL '30 days';

Procure por:

  • Seq Scan em tabelas grandes → candidato a índice
  • cost=X..Y alto → query cara
  • actual rows muito diferente de rows estimado → estatísticas desatualizadas (ANALYZE tabela)

Tipos de Índice

B-tree (Padrão)

Para igualdade, ranges e ordenação:

-- Índice simples
CREATE INDEX idx_pedidos_usuario ON pedidos(usuario_id);

-- Índice composto — ordem importa!
-- Útil para: WHERE usuario_id = X AND status = Y
-- Útil para: WHERE usuario_id = X (apenas primeiro campo)
-- NÃO útil para: WHERE status = Y (apenas segundo campo)
CREATE INDEX idx_pedidos_usuario_status ON pedidos(usuario_id, status);

-- Índice com ordenação para ORDER BY + LIMIT
CREATE INDEX idx_pedidos_criado_desc ON pedidos(criado_em DESC);

Índice Parcial

Indexa apenas um subconjunto das linhas — menor e mais rápido:

-- Apenas pedidos pendentes (talvez 5% do total)
CREATE INDEX idx_pedidos_pendentes
ON pedidos(usuario_id, criado_em)
WHERE status = 'pendente';

-- Apenas usuários ativos
CREATE INDEX idx_usuarios_email_ativos
ON usuarios(email)
WHERE ativo = true;

GIN — Full Text Search e Arrays

-- Full text search em português
ALTER TABLE artigos ADD COLUMN ts_busca tsvector
  GENERATED ALWAYS AS (
    to_tsvector('portuguese', coalesce(titulo, '') || ' ' || coalesce(conteudo, ''))
  ) STORED;

CREATE INDEX idx_artigos_fts ON artigos USING GIN(ts_busca);

-- Busca
SELECT titulo FROM artigos
WHERE ts_busca @@ plainto_tsquery('portuguese', 'postgresql performance');

-- Arrays
CREATE INDEX idx_tags ON posts USING GIN(tags);
SELECT * FROM posts WHERE tags @> ARRAY['postgresql', 'performance'];

BRIN — Tabelas Muito Grandes com Dados Sequenciais

-- Para tabelas de logs onde criado_em é quase sequencial
-- Índice minúsculo, eficiente para ranges de datas
CREATE INDEX idx_logs_criado_brin ON logs USING BRIN(criado_em);

Cobrindo Queries com Include

-- Index-only scan: evita acessar a tabela completamente
CREATE INDEX idx_pedidos_cobrindo
ON pedidos(usuario_id, status)
INCLUDE (valor_total, criado_em);

-- Esta query nunca toca a tabela principal
SELECT status, valor_total, criado_em
FROM pedidos
WHERE usuario_id = 123;

Identificando Índices Não Usados

-- Índices que nunca foram usados desde o último reset
SELECT schemaname, tablename, indexname, idx_scan
FROM pg_stat_user_indexes
WHERE idx_scan = 0
ORDER BY pg_relation_size(indexrelid) DESC;

Índices não usados têm custo: degradam writes e ocupam espaço. Remova os desnecessários.

Índices Concorrentes (Produção)

-- Cria o índice sem bloquear writes — demora mais, mas é seguro em produção
CREATE INDEX CONCURRENTLY idx_pedidos_novo ON pedidos(campo);

-- Remover também pode ser concorrente
DROP INDEX CONCURRENTLY idx_pedidos_antigo;

pg_stat_statements: Encontrando Queries Lentas

-- Habilite no postgresql.conf:
-- shared_preload_libraries = 'pg_stat_statements'

CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

-- Top 10 queries mais lentas
SELECT
  LEFT(query, 80) AS query,
  calls,
  ROUND(mean_exec_time::numeric, 2) AS media_ms,
  ROUND(total_exec_time::numeric / 1000, 2) AS total_seg
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;

Manutenção

-- Atualiza estatísticas usadas pelo planner
ANALYZE tabela;

-- Reconstrói índices fragmentados (cria novo, troca, remove antigo)
REINDEX INDEX CONCURRENTLY idx_pedidos_usuario;

-- Verifica inchaço de índices
SELECT indexrelid::regclass AS indice,
       pg_size_pretty(pg_relation_size(indexrelid)) AS tamanho
FROM pg_stat_user_indexes
ORDER BY pg_relation_size(indexrelid) DESC
LIMIT 20;

Conclusão

Índices são a ferramenta mais poderosa para melhorar performance de banco de dados. Comece sempre com EXPLAIN ANALYZE, crie índices parciais quando possível, e monitore regularmente índices não usados. Um índice criado errado pode piorar a situação — entenda o plano de execução antes de agir.