🧠FFVAcademy
🔬

EXPLAIN ANALYZE: lendo o plano e otimizando query

17 min de leitura·+85 XP

EXPLAIN ANALYZE é o raio-X de uma query PostgreSQL. Saber ler o output — identificar Seq Scans desnecessários, JOINs com estimativas ruins, sorts evitáveis — transforma debug de performance de tentativa e erro em diagnóstico preciso.

EXPLAIN vs EXPLAIN ANALYZE

-- EXPLAIN — só estima, NÃO executa
EXPLAIN SELECT * FROM pedidos WHERE cliente_id = 42;
-- Seq Scan on pedidos  (cost=0.00..850.00 rows=12 width=48)
--   Filter: (cliente_id = 42)

-- EXPLAIN ANALYZE — executa e mede tempos reais
EXPLAIN ANALYZE SELECT * FROM pedidos WHERE cliente_id = 42;
-- Seq Scan on pedidos  (cost=0.00..850.00 rows=12 width=48)
--                      (actual time=0.015..8.234 rows=156 loops=1)
--   Filter: (cliente_id = 42)
--   Rows Removed by Filter: 9844
-- Planning Time: 0.3 ms
-- Execution Time: 8.5 ms

-- ⚠️ EXPLAIN ANALYZE EXECUTA a query — cuidado com INSERT/UPDATE/DELETE!
-- Para mutations: envolver em transação e ROLLBACK
BEGIN;
EXPLAIN ANALYZE DELETE FROM pedidos WHERE status = 'cancelado';
ROLLBACK;

-- Opções completas para diagnóstico:
EXPLAIN (ANALYZE, BUFFERS, VERBOSE, FORMAT TEXT)
SELECT p.id, c.nome, SUM(ip.preco * ip.quantidade) AS total
FROM pedidos p
JOIN clientes c ON p.cliente_id = c.id
JOIN itens_pedido ip ON ip.pedido_id = p.id
WHERE p.criado_em >= '2024-01-01'
GROUP BY p.id, c.nome;

Lendo o output: nós e custos

-- Exemplo de output anotado:
-- Hash Join  (cost=125.00..890.00 rows=1250 width=60)
--            (actual time=5.2..45.8 rows=1180 loops=1)
--   Hash Cond: (p.cliente_id = c.id)
--   ->  Seq Scan on pedidos p  (cost=0.00..750.00 rows=10000 width=32)
--                               (actual time=0.01..12.3 rows=10000 loops=1)
--   ->  Hash  (cost=75.00..75.00 rows=4000 width=28)
--             (actual time=2.1..2.1 rows=4000 loops=1)
--         ->  Seq Scan on clientes c  (cost=0.00..75.00 rows=4000 width=28)
--                                     (actual time=0.01..1.2 rows=4000 loops=1)
-- Planning Time: 1.2 ms
-- Execution Time: 46.5 ms

-- Interpretação das colunas:
-- cost=startup..total: unidades de "custo I/O estimado"
-- rows: número de linhas estimado
-- width: largura média em bytes por linha
-- actual time=startup..total: milissegundos reais (min..max se loops>1)
-- actual rows: linhas reais retornadas
-- loops: quantas vezes o nó foi executado (nested loop pode ser 1000x)

-- BUFFERS mostra:
-- shared hit: leituras do shared_buffers (cache em memória) ← bom
-- shared read: leituras do disco ← ruim se muito alto
-- shared written: blocos sujos escritos

Scan types e quando cada um aparece

Scan TypeQuando apareceCusto relativo
Seq ScanSem índice, ou muitas linhas retornadas (>10-20% da tabela)Baixo por linha, alto total
Index ScanÍndice existe, poucas linhas, random_page_cost baixoAlto por linha, baixo total
Index Only ScanCovering index — não precisa acessar heapMais eficiente que Index Scan
Bitmap Index ScanÍndice existe mas retorna muitas linhas — agrupa I/OsIntermediário
Bitmap Heap ScanApós Bitmap Index Scan — acessa heap por blocoMelhor que Index Scan para médias quantidades
-- Identificar queries lentas automaticamente:
-- postgresql.conf:
-- log_min_duration_statement = 1000  (loga queries > 1s)
-- auto_explain.log_min_duration = 500  (loga planos de queries > 500ms)

-- Encontrar tabelas sem autovacuum recente (estatísticas desatualizadas):
SELECT
    schemaname,
    relname,
    n_live_tup,
    n_dead_tup,
    last_autoanalyze
FROM pg_stat_user_tables
WHERE last_autoanalyze < NOW() - INTERVAL '1 day'
   OR last_autoanalyze IS NULL
ORDER BY n_dead_tup DESC;

-- Forçar atualização de estatísticas:
ANALYZE tabela;      -- atualiza estatísticas da tabela
VACUUM ANALYZE;      -- vacuum + analyze em todo o banco

-- pg_stat_statements — queries mais lentas:
SELECT
    query,
    calls,
    total_exec_time / calls AS avg_ms,
    rows / calls AS avg_rows
FROM pg_stat_statements
WHERE calls > 100
ORDER BY avg_ms DESC
LIMIT 20;
Workflow de otimização: (1) identifique a query lenta com pg_stat_statements. (2) rode EXPLAIN (ANALYZE, BUFFERS). (3) procure: Seq Scans em tabelas grandes, loops muito altos em Nested Loop, estimativas muito diferentes dos reais. (4) crie índices ou ANALYZE para estatísticas. (5) verifique melhora. Não otimize sem medir — o planner frequentemente está certo.
💡
Próximo: Transações e isolation levels — ACID de verdade: o que dirty read, phantom read e serialization anomaly significam na prática.
🧩

Quiz rápido

3 perguntas · Acerte tudo e ganhe o badge 🎯 Gabarito

Continue lendo