Índice de Banco de Dados: Como Funciona e Quando Criar
O que é um índice de banco de dados
Um índice de banco de dados é uma estrutura separada que guarda uma cópia ordenada de uma ou mais colunas de uma tabela, com um ponteiro de volta para a linha original. Em vez de ler a tabela inteira para achar um registro, o banco consulta essa estrutura ordenada e vai direto ao ponto.
Sem índice, uma busca por WHERE email = 'x@y.com' em uma tabela de dois milhões de
linhas obriga o banco a ler linha por linha até achar (ou não achar) o valor. Isso se
chama sequential scan. Com um índice na coluna email, a mesma busca vira uma travessia
em árvore, algumas dezenas de comparações em vez de dois milhões.
O ganho é real, mas não é de graça. Cada índice precisa ser atualizado a cada INSERT,
UPDATE ou DELETE na coluna indexada. Um índice a mais é uma escrita a mais em toda
gravação. Por isso a pergunta certa nunca é "índice ajuda?". É "essa coluna é consultada
com frequência suficiente para pagar o custo de escrita?".
Como o banco usa o índice para acelerar uma consulta
A maioria dos bancos relacionais, PostgreSQL incluído, usa uma estrutura chamada B-tree como índice padrão. Ela mantém os valores ordenados em um formato de árvore balanceada, onde cada nível reduz o espaço de busca por um fator grande.
Isso explica por que a busca em uma B-tree é logarítmica e não linear. Dobrar o tamanho da tabela não dobra o tempo de busca: acrescenta, na prática, uma ou duas comparações a mais. É a diferença entre uma consulta que piora conforme os dados crescem e uma que se mantém estável.
O índice só ajuda, porém, quando o otimizador de consultas decide usá-lo. Isso depende
do formato da consulta, da distribuição dos dados e de estatísticas internas do banco
sobre a tabela. Uma coluna com poucos valores distintos, como um campo booleano de
ativo, raramente compensa um índice sozinho: o banco calcula que ler a tabela toda é
mais barato do que ir e voltar pelo índice.
Como criar um índice na prática
Antes de criar qualquer índice, é preciso confirmar que a consulta realmente precisa dele. O passo a passo abaixo funciona em PostgreSQL e é próximo o suficiente em MySQL e outros bancos relacionais.
- Rode a consulta lenta com
EXPLAIN ANALYZEna frente e procure porSeq Scanno plano de execução. Anote o tempo total e o número de linhas lidas. - Identifique a coluna usada no
WHERE, noJOINou noORDER BYque está gerando a leitura completa. - Crie o índice:
CREATE INDEX idx_users_email ON users (email);. - Rode o mesmo
EXPLAIN ANALYZEde novo. O plano deve trocarSeq ScanporIndex ScanouBitmap Index Scan, com tempo de execução menor. - Compare o tempo antes e depois. Se a diferença for marginal, o índice não estava
resolvendo o gargalo real, e vale reverter com
DROP INDEXpara não pagar o custo de escrita à toa.
Esse ciclo de medir antes e depois é o que separa índice criado por hipótese de índice
criado por evidência. Sem o EXPLAIN ANALYZE, fica fácil criar dez índices e não saber
qual dos dez realmente importa.
Quando não vale a pena criar um índice
Toda tabela pequena, algo na casa de poucos milhares de linhas, tende a rodar rápido com ou sem índice, porque o banco consegue manter a tabela inteira em memória. Criar índice nesse caso adiciona custo de escrita sem ganho perceptível de leitura.
Tabelas com muita escrita e pouca leitura são outro caso a evitar. Uma tabela de log que recebe milhares de inserções por minuto e raramente é consultada por coluna específica paga o preço de manter o índice atualizado sem nunca colher o benefício.
Colunas com baixa cardinalidade, poucos valores distintos possíveis, também costumam
não compensar. Um índice em uma coluna status que só assume três valores dificilmente
supera um sequential scan, porque o banco ainda precisa visitar uma fração grande da
tabela para montar o resultado.
Como escolher a ordem das colunas em um índice composto
Um índice composto, feito sobre mais de uma coluna, só é útil na ordem certa. A regra prática: coloque primeiro a coluna usada em filtro de igualdade, depois a usada em filtro de intervalo ou ordenação.
Suponha uma consulta que filtra por tenant_id (igualdade) e ordena por created_at
(intervalo). Um índice (tenant_id, created_at) atende bem: o banco localiza o bloco do
tenant e já percorre as linhas na ordem certa. Invertido, (created_at, tenant_id), o
mesmo índice perde a capacidade de isolar o tenant primeiro e o ganho cai.
Isso importa porque um índice composto na ordem errada não gera erro nenhum. Ele
simplesmente não é usado pelo otimizador, ou é usado de forma parcial, e a equipe só
descobre isso ao rodar EXPLAIN ANALYZE de novo meses depois.
O que priorizar quando a consulta ainda está lenta
Se depois de indexar a coluna certa a consulta continua lenta, o problema provavelmente
não é falta de índice. Vale revisar se a consulta traz colunas demais com SELECT *, se
existe JOIN desnecessário, ou se falta cache de aplicação para resultados que não
mudam a cada requisição. Aliás, um back-end rápido não compensa um front-end que demora
para exibir o resultado: se o tema for a experiência completa de carregamento, vale
também conferir como otimizar o LCP do site.
Índice de banco de dados resolve um problema específico: leitura lenta por falta de estrutura de busca. Não resolve escrita lenta nem consulta mal escrita. Para esses dois casos o ajuste fica na query ou no cache, não no índice. Criado na coluna certa, com a ordem certa em um índice composto, ele continua sendo uma das mudanças de maior retorno por linha de código em um sistema com banco relacional.
Se sua aplicação tem consulta lenta e você quer um diagnóstico de arquitetura e banco de dados, fale com a gente.