Marcio Cunha

Banco lento? Como otimizar consultas SQL com índices compostos

Descubra como estruturar índices B-Tree compostos no PostgreSQL considerando a cardinalidade das colunas e acelerar consultas SQL lentas.

Marcio Cunha•6 min
Também disponível em:EnglishEspañol
Resumo
  • Indices compostos organizam linhas baseados na ordem exata das colunas definidas na criacao do objeto
  • A cardinalidade mede a quantidade de valores unicos em uma coluna para orientar a posicao do indice
  • Consultas que filtram colunas na sequencia correta evitam varreduras completas desnecessarias na tabela
  • A ferramenta EXPLAIN ANALYZE revela exatamente se o banco esta usando o indice ou lendo dados em disco
  • Manter indices excessivos degrada a performance de gravacao por exigir atualizacoes constantes nas arvores

Por que consultas SQL ficam lentas com o crescimento das tabelas?

Quando um sistema web nasce, as consultas ao banco de dados parecem instantâneas porque a tabela possui apenas algumas centenas de registros. Na prática, o motor do banco consegue carregar esses dados inteiros para a memória RAM sem esforço algum. No entanto, conforme a aplicação ganha escala e milhões de linhas se acumulam, o banco precisa fazer uma varredura completa, conhecida como Sequential Scan, onde cada linha armazenada no disco precisa ser verificada individualmente. Isso consome processamento e tempo de disco de forma alarmante.

Para evitar esse desgaste, utilizamos índices, que funcionam como o sumário de um livro volumoso: em vez de ler todas as páginas para encontrar um termo específico, o banco consulta uma estrutura ordenada que aponta diretamente para o endereço físico do registro. Quando filtramos por mais de uma coluna simultaneamente, um índice simples em apenas uma coluna deixa de ser suficiente. É nesse cenário que entram os índices compostos, permitindo indexar duas ou mais colunas em conjunto para atender múltiplos critérios de busca em uma única estrutura otimizada. Caso queira investigar problemas maiores de hardware subjacente, vale a pena conferir o guia sobre como achar gargalos de disco no Linux para garantir que o armazenamento não seja o limitador.

Como funcionam os índices B-Tree compostos no PostgreSQL?

O PostgreSQL utiliza por padrão a estrutura B-Tree para seus índices, uma árvore balanceada que mantém os dados ordenados e permite buscas, inserções e remoções de forma extremamente rápida. Quando criamos um índice composto envolvendo duas ou mais colunas, a ordem em que essas colunas são declaradas na instrução SQL muda drasticamente o comportamento do banco. Na prática, o índice ordena primeiramente os registros pela primeira coluna informada. Se houver empates ou valores repetidos nessa primeira coluna, o banco passa a ordenar esses registros secundariamente pela segunda coluna, e assim por diante.

Essa regra de ordenação hierárquica cria a famosa 'regra do prefixo', que dita como o otimizador do PostgreSQL consegue aproveitar o índice. Se você criar um índice composto pelas colunas (empresa_id, status, data_criacao), o banco conseguirá usar esse índice eficientemente para consultas que filtram apenas por empresa_id, ou por empresa_id e status em conjunto. Contudo, se a sua consulta tentar filtrar apenas por status ignorando completamente a primeira coluna, o índice composto se torna inútil para essa busca específica, forçando o banco a descartá-lo. Para entender melhor como fluxos de dados complexos e filas exigem ajustes finos de infraestrutura, veja também as dicas de otimização em ajustes de prefetch no RabbitMQ para zerar latência.

O impacto da cardinalidade na ordem das colunas

A cardinalidade é um conceito estatístico que mede o grau de unicidade dos dados em uma coluna. Colunas com alta cardinalidade possuem muitos valores distintos, como o número de CPF, um endereço de e-mail ou uma chave primária. Em contrapartida, colunas com baixa cardinalidade possuem poucos valores repetidos muitas vezes, como o status de um pedido ('ativo', 'cancelado', 'pendente') ou o gênero de um usuário. Saber posicionar essas colunas corretamente no planejamento de um índice composto é o segredo dos engenheiros seniores para extrair o máximo de desempenho.

A regra de ouro na engenharia de dados é sempre posicionar as colunas de maior cardinalidade o mais à esquerda possível no índice composto. Quando colocamos uma coluna altamente restritiva no início, o PostgreSQL consegue eliminar uma fatia gigantesca de registros logo na primeira ramificação da árvore B-Tree. Se colocarmos uma coluna de baixa cardinalidade primeiro, o índice precisará lidar com milhares de ramificações idênticas antes de conseguir filtrar a informação relevante, reduzindo a eficiência da busca. O equilíbrio entre a ordem das colunas e os filtros mais comuns da aplicação determina o sucesso da estratégia de indexação.

Analisando planos de execução reais com EXPLAIN ANALYZE

Não basta criar um índice e torcer para que o banco de dados o utilize; é obrigatório medir o impacto real através do comando EXPLAIN ANALYZE. Essa instrução executa a sua consulta em ambiente controlado e devolve um relatório detalhado mostrando exatamente quais passos o motor do banco seguiu, quanto tempo cada etapa levou e se o índice planejado foi de fato acionado. Sem essa análise, qualquer otimização não passa de adivinhação baseada em achismos.

Para usar essa ferramenta na prática, basta adicionar a palavra EXPLAIN ANALYZE antes de qualquer comando SELECT. O resultado apresentará termos como Index Scan, que indica que o banco utilizou o índice com sucesso, ou Seq Scan, que sinaliza que o banco ignorou os índices e leu a tabela inteira linha por linha. Além disso, o relatório exibe métricas de custo estimado e tempo real de execução em milissegundos. Abaixo, veja um exemplo prático de como estruturar essa verificação no console do PostgreSQL:

EXPLAIN ANALYZE
SELECT id, cliente_id, total, criado_em
FROM pedidos
WHERE status = 'pendente' AND criado_em > '2026-01-01';

Se o plano de execução retornar um Seq Scan indesejado, significa que a sua consulta não está alinhada com a ordem das colunas do índice existente ou que as estatísticas internas do banco precisam ser atualizadas com o comando ANALYZE para que o planejador compreenda a distribuição real dos dados.

Erros comuns e armadilhas ao criar índices compostos

Um erro clássico cometido por desenvolvedores menos atentos é criar índices para absolutamente todas as colunas de uma tabela, imaginando que isso trará velocidade infinita para o sistema. Na prática, cada índice adicionado consome espaço extra em disco e exige que o PostgreSQL realize trabalho adicional toda vez que um comando INSERT, UPDATE ou DELETE for executado. Como o índice precisa ser mantido ordenado, modificações na tabela geram reescritas nas árvores B-Tree, encarecendo as operações de escrita e degradando a performance geral da aplicação.

Outra armadilha frequente envolve o uso de funções ou operadores matemáticos aplicados diretamente sobre as colunas indexadas na cláusula WHERE. Se você possui um índice na coluna criado_em, mas executa uma consulta filtrando por WHERE DATE(criado_em) = '2026-01-10', o PostgreSQL não conseguirá utilizar o índice, pois a função DATE() altera o valor bruto armazenado na árvore. Para contornar esse problema, a alternativa correta é ajustar o filtro para comparar intervalos de data diretamente, permitindo que o motor utilize o índice composto sem barreiras. Para aprofundar esses conceitos na prática em produção, confira também nossa análise técnica sobre Como testar regulador AMS1117 na placa com multímetro.

Considerações finais sobre performance de banco de dados

A otimização de consultas SQL utilizando índices compostos e a análise rigorosa com EXPLAIN ANALYZE formam a base indispensável para manter sistemas escaláveis e responsivos. Compreender os conceitos de cardinalidade, a rigidez da regra do prefixo em estruturas B-Tree e o impacto operacional de índices redundantes permite que engenheiros de software tomem decisões técnicas fundamentadas, eliminando gargalos antes que eles afetem a experiência dos usuários finais da plataforma.

Investir tempo no planejamento e na monitoria contínua do banco de dados evita reescritas emergências de código em momentos de alta carga. Ao alinhar os padrões de consulta da aplicação com a arquitetura física dos dados no PostgreSQL, você garante estabilidade, baixo consumo de recursos e crescimento sustentável para o seu software ao longo dos anos.