Como recuperar estatísticas e índices corrompidos no PostgreSQL que deixaram consultas lentas

Por · Publicado em · 7 min de leitura

Painel de controle industrial com consoles, botões, mostradores e iluminação no teto
Metáfora de monitoramento e operação, adequada para diagnóstico e manutenção de bancos de dados Foto: Pexels / Pixabay

Consultas ficaram lentas e você suspeita de estatísticas ruins ou índices corrompidos? Aqui vai um caminho prático: confirmar o diagnóstico, reconstruir estatísticas com ANALYZE, reindexar com baixo impacto e prevenir recorrência. Mostro sinais típicos, comandos seguros e quando usar REINDEX CONCURRENTLY ou recriar índices. Serve para entender e agir sem adivinhação.

Sinais de que o gargalo é estatística ou índice (não a aplicação)

Antes de mudar código, verifique se o otimizador perdeu o rumo. Estatísticas imprecisas e índices danificados fazem o plano sair do trilho, escolhendo varreduras sequenciais caras ou filtros ruins.

Procure estes sintomas em produção:

  • Plano mudou de Index Scan para Seq Scan sem alteração de dados proporcional.
  • Estimativas de linhas (rows) no EXPLAIN diferem muito do real.
  • Índice quase não é usado (idx_scan cai) após manutenção ou incidente.
  • A lentidão começou logo após um pico de updates/deletes ou um crash do servidor.
  • Tabelas com muitos dead tuples em pg_stat_user_tables.

Causas comuns: por que estatísticas e índices se degradam

Entender a origem evita repetir o problema. Estas são causas frequentes e plausíveis no PostgreSQL:

  • Autovacuum/autoanalyze desativado ou mal calibrado em tabelas grandes.
  • Carga com muitas atualizações, deixando índices inchados (bloat).
  • Criação de índice interrompida; índice fica indisponível ou inválido.
  • Crash, falhas de disco ou cache de escrita não confiável durante manutenção.
  • Upgrade parcial ou operações DDL sem janela e rollback claro.

Diagnóstico rápido e seguro em produção

Confirme a hipótese antes de alterar. Use EXPLAIN para ver o plano, cheque dead tuples, índices inválidos e valide a integridade de B-Trees com amcheck.

EXPLAIN ANALYZE executa a consulta. Em rotas críticas, rode primeiro sem ANALYZE. Evite testes pesados em horário de pico.

-- 1) Inspecione o plano da consulta problemática
EXPLAIN (ANALYZE, BUFFERS)
SELECT /* sua query aqui */ 1;

-- 2) Tabelas com muitos dead tuples (candidatas a VACUUM/ANALYZE)
SELECT relname, n_live_tup, n_dead_tup
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 10;

-- 3) Índices inválidos ou não prontos
SELECT c.relname AS index_name, i.indisvalid, i.indisready
FROM pg_index i
JOIN pg_class c ON c.oid = i.indexrelid
WHERE NOT i.indisvalid OR NOT i.indisready;

-- 4) Verifique a integridade de B-Trees (exige superuser)
CREATE EXTENSION IF NOT EXISTS amcheck;
SELECT bt_index_check(indexrelid)
FROM pg_index
WHERE indisvalid;

Recuperar estatísticas: ANALYZE e pequenos ajustes

Quando o plano erra por má estimativa, regenere estatísticas. ANALYZE e VACUUM (ANALYZE) rodam online e não bloqueiam leituras/escritas normais. Em tabelas enormes, priorize as mais usadas.

Se colunas têm distribuição irregular, aumente a amostragem por coluna. Ajustes finos reduzem regressões futuras.

-- Atualize estatísticas nas tabelas críticas
ANALYZE VERBOSE schema.tabela_mais_lenta;
-- Opcional, se há muitos dead tuples
VACUUM (ANALYZE, VERBOSE) schema.tabela_mais_lenta;

-- Aumente a qualidade das estatísticas de uma coluna skewed
ALTER TABLE schema.tabela_mais_lenta
  ALTER COLUMN col_filtro SET STATISTICS 200;  -- 1–10000

-- Tornar a amostragem padrão mais rica (recarrega config)
ALTER SYSTEM SET default_statistics_target = 200;
SELECT pg_reload_conf();

Recuperar índices: REINDEX com baixo impacto e alternativas

Se amcheck apontar problemas ou o índice estiver inválido, reconstrua. Prefira minimizar bloqueios nas tabelas quentes.

REINDEX CONCURRENTLY reduz impacto, porém não cobre todos os casos. Quando indisponível, crie um índice novo em paralelo e depois remova o antigo. REINDEX em banco inteiro pode bloquear; use com critério.

  • Índice levemente inchado: REINDEX CONCURRENTLY no índice ou na tabela.
  • Índice inválido/corrompido: recrie com CREATE INDEX CONCURRENTLY e depois DROP.
  • Catálogos do sistema afetados: REINDEX SYSTEM, planejando janela e testes.
Ação Impacto de lock Quando usar
ANALYZE Mínimo Estimativas ruins, plano trocado sem mudança de dados
VACUUM (ANALYZE) Baixo Muitos dead tuples e uso intenso de escrita
REINDEX CONCURRENTLY Baixo a moderado Bloat/erro em B-Tree de tabelas críticas
CREATE INDEX CONCURRENTLY + DROP Baixo a moderado Substituir índice problemático sem bloquear writes
REINDEX {TABLE|SCHEMA|DATABASE|SYSTEM} Alto Múltiplos índices afetados; exige janela de manutenção
-- Reconstrução com menor bloqueio (PostgreSQL >= 12)
REINDEX INDEX CONCURRENTLY schema.idx_cliente_cpf;
-- Ou na tabela inteira
REINDEX TABLE CONCURRENTLY schema.clientes;

-- Alternativa compatível: recriar e trocar
CREATE INDEX CONCURRENTLY idx_cliente_cpf_v2 ON schema.clientes (cpf);
-- Depois que o v2 estiver pronto e usado:
DROP INDEX CONCURRENTLY schema.idx_cliente_cpf;

Verificação pós-correção e rollback seguro

Depois de ANALYZE/REINDEX, valide que o plano voltou ao esperado e que o índice é usado. Compare tempos e estimativas antes/depois.

Mantenha um caminho de volta: nomes de índice versionados facilitam rollback. Evite apagar o índice antigo antes de confirmar a melhora sob carga real.

Métrica Onde ver Indício de sucesso
Plano (EXPLAIN) EXPLAIN (ANALYZE, BUFFERS) Estimativas próximas do real; menos Seq Scan
Uso do índice pg_stat_all_indexes.idx_scan Crescimento consistente no índice novo
Dead tuples pg_stat_user_tables.n_dead_tup Queda após VACUUM (ANALYZE)
Tempo da query Métrica de APM ou logs Redução estável, sem picos anômalos
-- Reavalie o plano e o tempo de execução
EXPLAIN (ANALYZE, BUFFERS)
SELECT /* sua query aqui */ 1;

-- Índices mais usados pós-ajuste
SELECT relname AS index_name, idx_scan
FROM pg_stat_all_indexes
ORDER BY idx_scan DESC
LIMIT 10;

Prevenção: autovacuum, rotinas e mudanças controladas

Previna nova degradação mantendo estatísticas frescas e índices saudáveis. Automatize verificações, ajuste thresholds e cuide de DDLs.

Crie rotinas fora do pico. Monitore tendências para agir antes da crise.

  • Calibre autovacuum para tabelas grandes (scale_factor e thresholds menores).
  • Agende ANALYZE direcionado após cargas massivas.
  • Automatize amcheck periódico em índices críticos.
  • Padronize DDL com versionamento de banco para mudanças seguras.
  • Investigue jobs que causam bloqueios recorrentes antes de manutenções.
-- Exemplo: tornar o autovacuum mais agressivo em uma tabela quente
ALTER TABLE schema.clientes SET (
  autovacuum_vacuum_scale_factor = 0.05,
  autovacuum_analyze_scale_factor = 0.02,
  autovacuum_vacuum_threshold = 1000,
  autovacuum_analyze_threshold = 500
);

Perguntas frequentes

Como saber se a lentidão é por estatísticas ruins ou por SQL ineficiente?

Compare o plano atual com o esperado via EXPLAIN. Se as estimativas divergem muito do real e o plano mudou sem alteração de dados, regenere estatísticas. Se o plano continua ruim mesmo com boas estimativas, revise a query e índices existentes.

ANALYZE e VACUUM bloqueiam a aplicação?

ANALYZE e VACUUM (sem FULL) rodam online, sem bloquear leituras e escritas normais. Há locks leves de curto prazo para metadados. Em tabelas muito ativas, rode fora do pico e monitore impacto.

Quando usar REINDEX CONCURRENTLY?

Use quando há bloat ou suspeita de corrupção em índices B-Tree de tabelas críticas e você precisa reduzir bloqueios. Se o índice está inválido ou ausente, crie um novo com CREATE INDEX CONCURRENTLY e remova o antigo depois.

Devo reindexar o banco inteiro?

Só com justificativa forte e janela de manutenção. REINDEX no banco inteiro pode causar bloqueios amplos. Prefira atacar índices ou tabelas específicas, e apenas escalar para schema/database/system após confirmar necessidade.

Autovacuum ligado não deveria evitar isso?

Ajuda, mas a configuração padrão pode ser tímida para tabelas grandes e workloads intensos. Ajuste scale factors e thresholds, faça ANALYZE direcionado após cargas massivas e monitore amcheck e uso de índices.

Posso apagar pg_statistic para reconstruir do zero?

Não. pg_statistic é um catálogo do sistema. O caminho correto é rodar ANALYZE (ou VACUUM ANALYZE) e, se necessário, aumentar a amostragem com default_statistics_target ou SET STATISTICS por coluna.

Precisa recuperar performance sem parar o sistema?

Se você quer um plano seguro para ANALYZE, REINDEX e ajustes de autovacuum com baixo impacto, vamos conversar sobre seu cenário e priorizar ações de maior ganho.

Falar sobre meu caso

Veja também