Usar o CREATE INDEX sem pensar te dá um B-tree toda vez — e o B-tree é a ferramenta errada para busca textual, JSONB, arrays e tabelas de séries temporais com bilhões de linhas. Conhecer os quatro tipos de índice do PostgreSQL é a diferença entre uma consulta que voa e uma que ignora seu índice por completo.
Não existe tamanho único
O PostgreSQL traz vários métodos de acesso de índice. Escolha errado e você não ganha velocidade ou paga 10× em disco e custo de escrita à toa. Os três que mais importam no dia a dia são B-tree, GIN e BRIN — e a pergunta decisiva é sempre a mesma: qual é o formato do dado e qual é o formato da consulta?
B-tree — o cavalo de batalha padrão
Usado automaticamente pelo CREATE INDEX. Perfeito para comparações de igualdade e faixa sobre valores escalares, e para ORDER BY.
CREATE INDEX idx_orders_created ON orders (created_at);
-- ajuda: WHERE created_at > now() - interval '7 days'
-- ORDER BY created_at DESC
-- WHERE customer_id = 42 (igualdade)
Suporta índices multicoluna, em que a ordem das colunas importa: um índice em (a, b) ajuda WHERE a = ? e WHERE a = ? AND b = ?, mas não WHERE b = ? sozinho.
GIN — para valores que contêm muitos subvalores
Generalized Inverted Index. Use quando uma coluna guarda muitos itens pesquisáveis: documentos JSONB, arrays e vetores de busca textual.
-- Continência em JSONB
CREATE INDEX idx_doc_data ON documents USING gin (data jsonb_path_ops);
-- ajuda: WHERE data @> '{"status": "active"}'
-- Busca textual (full-text)
CREATE INDEX idx_articles_fts ON articles
USING gin (to_tsvector('portuguese', body));
-- ajuda: WHERE to_tsvector('portuguese', body) @@ plainto_tsquery('postgres')
-- Pertencimento em array
CREATE INDEX idx_post_tags ON posts USING gin (tags);
-- ajuda: WHERE tags @> ARRAY['sql']
Índices GIN são maiores e mais lentos para atualizar que o B-tree. Se você escreve com frequência, considere ajustar fastupdate e gin_pending_list_limit.
BRIN — índices minúsculos para tabelas enormes e naturalmente ordenadas
Block Range INdex. Em vez de indexar cada linha, ele guarda o mínimo/máximo por faixa de blocos. O resultado é surpreendentemente pequeno — muitas vezes megabytes para uma tabela de um bilhão de linhas — mas só funciona quando a coluna se correlaciona com a ordem física das linhas (pense em logs append-only ordenados por tempo).
CREATE INDEX idx_events_time_brin ON events
USING brin (created_at) WITH (pages_per_range = 128);
-- ajuda: WHERE created_at BETWEEN '2026-01-01' AND '2026-02-01'
-- em tabela append-only onde as linhas chegam em ordem de tempo
Um índice BRIN pode ter 1/1000 do tamanho do B-tree equivalente. Em uma tabela de série temporal bem correlacionada ele entrega quase todo o benefício por uma fração do armazenamento e do custo de escrita. Em uma coluna com ordem aleatória, é inútil.
Tabela rápida de decisão
| Sua consulta | Tipo de índice |
|---|---|
=, <, >, BETWEEN, ORDER BY em escalar | B-tree |
JSONB @>, array contém, full-text @@ | GIN |
| Faixas em tabela enorme append-only / ordenada por tempo | BRIN |
| Sobreposição de tipos geométricos/range, vizinho mais próximo | GiST |
| Apenas igualdade simples, menor alternativa ao B-tree | Hash |
O índice que você esqueceu
O custo oculto dos índices são os que ninguém usa: todo índice sem uso ainda deixa cada INSERT e UPDATE mais lento e incha os backups. O PG Monitoring relata o uso de cada índice (idx_scan) e sinaliza os que não são tocados há semanas, além dos índices faltantes que realmente ajudariam — quantificados em milissegundos economizados.