Uma consulta que devolve 12 linhas varreu 2 milhões delas. O SQL está legível e ainda assim o plano de execução escolheu Seq Scan. Antes de culpar a ORM ou o hardware, você lê o plano: EXPLAIN (ANALYZE, BUFFERS) imprime o caminho real que o Postgres percorreu, com tempo por nó e páginas lidas. Os três tipos de índice abaixo resolvem a maior parte dos problemas de leitura em PostgreSQL.
Ler o plano antes de criar índice
EXPLAIN mostra o plano estimado. EXPLAIN ANALYZE executa a consulta e mostra o tempo real de cada nó. A diferença entre estatística e realidade denuncia filtro que descarta a maioria das linhas depois do scan, ou buffer cache frio no momento do teste. Seq Scan em tabela de 50 mil linhas costuma ser a escolha certa: ler tudo em sequência sai mais barato que saltar por árvore. Seq Scan em tabela de 20 milhões com filtro que retorna 10 linhas denuncia índice ausente ou coluna fora do índice existente.
A documentação oficial do PostgreSQL (2025) descreve a família de métodos de índice: B-tree, Hash, GiST, SP-GiST, GIN e BRIN. Cada método atende um formato de busca, e o B-tree domina igualdade e faixa.
B-tree, GIN e a ordem das colunas
Índice B-tree atende =, operadores de faixa, BETWEEN e ORDER BY na direção indexada. Busca por padrão no meio do texto, como ILIKE '%valor%', foge do B-tree e exige GIN com a extensão pg_trgm. Colunas JSONB e arrays usam GIN: o índice inverte o documento e aponta para as linhas que contêm cada chave ou valor.
Ordem das colunas em índice composto segue a regra do planner: colunas de igualdade primeiro, colunas de faixa depois. Um índice em (tenant_id, created_at) atende filtro por tenant com ordenação por data; o índice invertido atende filtro por data de qualquer tenant e obriga varredura maior. Markus Winand documenta essa hierarquia no use-the-index-luke.com, referência que times de banco consultam há mais de uma década.
Índice parcial e índice coberto
Índice parcial cobre subconjunto da tabela: WHERE status = 'active' encolhe o índice e reduz escrita. Tabela onde consultas miram só o subconjunto ativo ganha com isso. Índice coberto, com cláusula INCLUDE, guarda colunas extras na folha do índice: o planner lê o índice sem voltar à tabela heap. A conta de folha em folha que sustenta isso aparece em Database Internals, de Alex Petrov (O'Reilly, 2019), com o detalhe de como o Postgres percorre as páginas.
Custo de escrita e índice esquecido
Cada INSERT, UPDATE e DELETE paga o custo de manter os índices da tabela. Uma tabela de eventos com 12 índices duplica o trabalho de escrita. pg_stat_user_indexes lista índices sem uso pelo planner; você mede a escrita antes e depois do drop e mantém só os que atendem consulta rara de auditoria, com justificativa registrada no repositório. Em tabelas de escrita pesada, o autovacuum precisa de folga; sem ele, o B-tree fragmenta e a folha ocupa mais espaço do que o dado.
Curtiu o conteúdo?
Construo produtos web e soluções com IA do jeito certo — arquitetura sólida, código sustentável e entrega real.
Vamos conversar