rochasolutions
Back-end

Índice de Banco de Dados: Como Funciona e Quando Criar

Danilo Rocha5 min de leitura

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.

  1. Rode a consulta lenta com EXPLAIN ANALYZE na frente e procure por Seq Scan no plano de execução. Anote o tempo total e o número de linhas lidas.
  2. Identifique a coluna usada no WHERE, no JOIN ou no ORDER BY que está gerando a leitura completa.
  3. Crie o índice: CREATE INDEX idx_users_email ON users (email);.
  4. Rode o mesmo EXPLAIN ANALYZE de novo. O plano deve trocar Seq Scan por Index Scan ou Bitmap Index Scan, com tempo de execução menor.
  5. 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 INDEX para 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.