Como recuperar estatísticas e índices corrompidos no PostgreSQL que deixaram consultas lentas
Por Kevin Marcondes · Publicado em · 7 min de leitura
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 casoVeja também
- API REST com spikes de latência por queries N+1 e GC no PHP — como diagnosticar e corrigir
- Erro 'Too many connections' no PostgreSQL durante pico de faturamento — guia prático de diagnóstico e correção
- Reduzir custos migrando Magento para OpenMage com plano de rollback e testes de aceitação
- Guia prático: como recuperar uma tabela excluída no PostgreSQL usando point-in-time recovery sem downtime