Ir para o conteúdo principal
Índices no PostgreSQL: como encontrar e corrigir consultas lentasBanco de Dados

Índices no PostgreSQL: como encontrar e corrigir consultas lentas

Aprenda a localizar consultas lentas no PostgreSQL, interpretar EXPLAIN ANALYZE e criar índices eficientes sem prejudicar as operações de escrita.

Publicado em 25 de setembro de 20268 min de leituraMax Alex

Quando um sistema web cresce, a lentidão raramente aparece de uma vez. Uma tela demora alguns segundos a mais, relatórios passam a pesar e, nos horários de pico, operações simples começam a travar. Em muitos casos, a causa está em consultas que leem muito mais dados do que deveriam.

Os índices no PostgreSQL ajudam o banco a localizar registros com menos trabalho, mas criá-los sem análise pode aumentar o custo de escrita e não resolver o problema. Este guia mostra como encontrar consultas lentas, interpretar o plano de execução e escolher índices com segurança.

Por que consultas no PostgreSQL ficam lentas

Uma consulta pode funcionar bem com milhares de registros e se tornar lenta quando a tabela chega a milhões. O crescimento do volume de dados expõe filtros sem índice, junções custosas, ordenações em grandes conjuntos e consultas que retornam mais colunas do que a aplicação realmente precisa.

Imagine uma tabela pedidos com histórico de vendas. Uma consulta que busca pedidos de um cliente dentro de um período pode acabar examinando a tabela inteira se não houver um índice compatível com as colunas usadas no WHERE.

SELECT id, status, created_at
FROM pedidos
WHERE cliente_id = 42
  AND created_at >= CURRENT_DATE - INTERVAL '30 days';

Nem toda consulta lenta é resolvida com um novo índice. Uma condição pouco seletiva, um JOIN inadequado, estatísticas desatualizadas ou uma paginação mal implementada também podem ser a origem do gargalo. Por isso, a análise deve partir do padrão real de acesso aos dados.

O banco também faz parte da otimização de desempenho do site: uma página rápida depende tanto do front-end quanto do tempo necessário para a aplicação buscar e processar as informações.

Vale lembrar que cada índice adicional ocupa espaço e precisa ser atualizado em operações de INSERT, UPDATE e DELETE. O objetivo não é indexar tudo, mas criar estruturas que atendam às consultas mais importantes.

À medida que tráfego, dados e integrações aumentam, a estratégia de banco influencia diretamente a escalabilidade da aplicação. Índices são uma parte desse trabalho, ao lado de cache, filas, arquitetura e observabilidade.

Como encontrar as consultas mais lentas

Antes de alterar uma tabela, descubra quais consultas têm maior impacto. Priorize evidências: tempo total consumido, frequência de execução, quantidade de linhas lidas e comportamento nos períodos de maior uso.

Use pg_stat_statements para priorizar

A extensão pg_stat_statements agrupa estatísticas das consultas executadas e é uma das formas mais úteis de enxergar onde o banco passa mais tempo. Depois de habilitá-la conforme a configuração do ambiente, uma consulta inicial pode ser:

SELECT query,
       calls,
       total_exec_time,
       mean_exec_time,
       rows
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;

Uma query com execução individual de 80 ms pode ser mais relevante do que outra de 2 segundos se ela for chamada milhares de vezes por hora. Observe principalmente consultas com alto tempo total, muitas chamadas ou retorno de linhas excessivo.

Logs de consultas lentas e métricas da aplicação complementam essa visão. Registre o SQL executado, os parâmetros representativos, a rota ou processo que o disparou e o horário. Assim, você evita otimizar uma consulta isolada que não representa o uso real.

Trate essa investigação como parte do monitoramento da aplicação. Comparar os dados antes e depois de uma mudança torna mais fácil confirmar se o ganho chegou ao usuário final.

Desempenho também precisa de revisão recorrente. Uma manutenção técnica contínua ajuda a identificar regressões antes que elas se tornem indisponibilidade ou perda de conversão.

Como usar EXPLAIN ANALYZE para entender o plano de execução

Depois de escolher uma consulta relevante, use EXPLAIN para ver o plano que o otimizador pretende executar. Com EXPLAIN ANALYZE, o PostgreSQL executa a consulta e acrescenta tempos e quantidades reais de linhas.

EXPLAIN (ANALYZE, BUFFERS)
SELECT id, status, created_at
FROM pedidos
WHERE cliente_id = 42
  AND created_at >= DATE '2026-01-01';

Como EXPLAIN ANALYZE executa o comando, tenha cuidado com instruções que alteram dados. Para investigar um UPDATE ou DELETE, use uma transação de teste que possa ser desfeita ou trabalhe em ambiente seguro.

Principais sinais do plano

Seq Scan indica uma varredura sequencial da tabela. Isso não é automaticamente ruim: em tabelas pequenas, ou quando a consulta retorna grande parte dos registros, esse pode ser o plano mais barato. Em uma tabela grande, porém, um Seq Scan para uma busca altamente específica merece atenção.

Index Scan mostra que o PostgreSQL acessa registros por um índice. Já Bitmap Index Scan, normalmente acompanhado de Bitmap Heap Scan, pode ser eficiente quando muitos registros dispersos precisam ser encontrados antes da leitura da tabela.

Compare as linhas estimadas com as linhas reais. Diferenças grandes podem indicar estatísticas desatualizadas, distribuição de dados incomum ou filtros que o planejador não conseguiu estimar bem. Também observe actual time, loops e os blocos lidos em BUFFERS.

O nó exibido no topo do plano representa o resultado final, mas o gargalo pode estar em um nó interno. Procure operações com maior tempo acumulado, muitas repetições ou remoção excessiva de linhas por filtro. Essa leitura é uma habilidade importante no desenvolvimento de sistemas web que precisam continuar responsivos sob carga.

Quais tipos de índices usar no PostgreSQL

O melhor índice depende do formato da consulta, da distribuição dos dados e da frequência de leitura e escrita. Conheça os tipos mais comuns antes de criar qualquer estrutura.

B-tree: o padrão para a maioria dos casos

O B-tree atende bem filtros por igualdade, intervalos e muitas ordenações. É a escolha inicial para colunas usadas em condições como id = ?, created_at > ? e ORDER BY created_at.

CREATE INDEX idx_pedidos_cliente_created_at
ON pedidos (cliente_id, created_at DESC);

Esse índice pode atender uma listagem filtrada por cliente e ordenada pela data. A ordem das colunas importa: em geral, ela deve refletir os filtros mais seletivos e o padrão de ordenação efetivamente usado.

Índices compostos e parciais

Índices compostos combinam duas ou mais colunas. Para uma tela que filtra pedidos por status e ordena por data, um índice em (status, created_at) pode reduzir leituras e até evitar uma etapa extra de ordenação.

Índices parciais armazenam somente registros que atendem a uma condição. São úteis quando uma consulta recorrente trabalha com uma parcela pequena e previsível da tabela.

CREATE INDEX idx_pedidos_abertos_created_at
ON pedidos (created_at DESC)
WHERE status = 'aberto';

Antes de escolher a solução, considere a estrutura do banco de dados, os padrões de consulta e as necessidades da aplicação. Um índice excelente para uma tela específica pode ser redundante em outra.

GIN, GiST e índices de expressão

GIN costuma ser apropriado para consultas em JSONB, arrays e determinados casos de busca textual. GiST atende cenários especializados, como alguns tipos geoespaciais e operadores de similaridade. Ambos devem ser escolhidos a partir da consulta e do operador utilizados, não apenas do tipo de coluna.

Quando o filtro aplica uma função à coluna, um índice comum pode não ajudar. Para buscas recorrentes sem diferenciação entre maiúsculas e minúsculas, por exemplo, um índice de expressão pode alinhar o índice à consulta:

CREATE INDEX idx_usuarios_lower_email
ON usuarios (lower(email));

Ele deve ser acompanhado por uma consulta compatível, como WHERE lower(email) = lower($1).

Como criar índices sem comprometer o desempenho

Uma otimização segura segue um ciclo simples: reproduza a consulta, registre uma linha de base, crie o índice proposto, valide o novo plano e acompanhe o efeito nas leituras e escritas.

  1. Capture uma consulta real e parâmetros representativos.
  2. Execute EXPLAIN (ANALYZE, BUFFERS) e registre tempo, linhas e blocos lidos.
  3. Crie apenas o índice que atende ao padrão identificado.
  4. Repita a medição nas mesmas condições.
  5. Monitore operações de escrita e o uso do índice ao longo do tempo.

Em produção, CREATE INDEX CONCURRENTLY pode ser a opção adequada para reduzir bloqueios durante a criação do índice. Esse comando possui restrições operacionais e não pode ser executado dentro de uma transação explícita; planeje a migração e valide o procedimento no seu ambiente.

CREATE INDEX CONCURRENTLY idx_pedidos_cliente_created_at
ON pedidos (cliente_id, created_at DESC);

Depois da mudança, não se limite a verificar se o índice existe. Confirme que o plano passou a usá-lo quando isso faz sentido e que o tempo observado pela aplicação melhorou. Avalie também índices duplicados, pouco utilizados ou cobertos por outro índice mais abrangente.

Por fim, mantenha VACUUM e ANALYZE compatíveis com a carga do banco. Estatísticas confiáveis ajudam o planejador a decidir quando um índice é vantajoso e quando uma varredura sequencial é melhor.

Erros comuns ao tentar acelerar consultas com índices

O erro mais frequente é criar índices indiscriminadamente. Isso aumenta o armazenamento, torna escritas mais caras e dificulta a manutenção, sem garantir que o PostgreSQL os use.

  • Indexar colunas de baixa seletividade sem contexto: uma coluna booleana ou de status com poucos valores pode não se beneficiar de um índice simples. Um índice parcial, ou uma consulta diferente, pode ser mais útil.
  • Ignorar funções no filtro: aplicar lower(), conversões ou cálculos à coluna pode impedir o uso de um índice comum. Avalie um índice de expressão ou reescreva a condição.
  • Usar SELECT amplo: retornar colunas desnecessárias aumenta transferência, memória e trabalho de processamento.
  • Não revisar JOINs e paginação: índices ajudam, mas não corrigem junções com cardinalidade inesperada, condições incorretas ou paginação por grandes deslocamentos.
  • Confiar somente no custo estimado: valide sempre com tempos reais e métricas da aplicação.
  • Esquecer a manutenção: tabelas com muitas alterações precisam de autovacuum e estatísticas bem ajustados.

O índice certo surge de uma hipótese verificável: existe uma consulta importante, há evidência de leitura excessiva e o novo índice altera favoravelmente o plano ou o tempo real. Sem essa sequência, é fácil acumular complexidade sem ganho mensurável.

Conclusão

Índices no PostgreSQL são fundamentais para manter consultas previsíveis à medida que a base cresce, mas funcionam melhor quando fazem parte de um processo de observação e validação. Comece pelas consultas de maior impacto, leia o plano executado, escolha o índice adequado e meça o resultado.

Seu sistema está lento ou o banco de dados não acompanha o crescimento do negócio? A Codephix pode analisar a performance da sua aplicação, identificar gargalos no PostgreSQL e implementar melhorias técnicas para uma experiência mais rápida e estável.

Voltar ao blogAtualizado em 25 de setembro de 2026

Pronto para conversar sobre o seu projeto?

A Codephix transforma desafios operacionais em sistemas que funcionam. Fale com a nossa equipe.

WhatsApp