Começar pelo contrato e pela medição
Um relatório noturno fictício passou de segundos para minutos depois de crescer o volume de tarefas. Antes de criar um índice, confirma a query, os parâmetros, a população e o resultado exigido. Uma versão com LIMIT 10 não mede o mesmo trabalho de uma exportação completa. Recolhe o plano estimado e, num ambiente e âmbito autorizados, medições de execução representativas. Distingue tempo do servidor de transferência e processamento no cliente. Regista também se o ensaio usou cache aquecida e qual a carga concorrente. O objetivo é formular uma hipótese observável: reduzir linhas examinadas, corrigir uma estimativa ou evitar uma ordenação dispendiosa sem mudar a semântica.
Custo, linhas e loops têm unidades próprias
cost=2..140 é uma estimativa nas unidades do modelo do planner, não uma duração de 140 milissegundos. rows descreve a saída estimada do nó, que pode diferir das linhas examinadas. Num excerto sintético, um nó interno emite exatamente quatro linhas em cada uma de 250 execuções: actual rows=4.00 e loops=250 correspondem a 1000 emissões. Não concluas que são 1000 identidades distintas. Os custos dos nós superiores incluem os inferiores; somar toda a árvore duplica trabalho. Considera também consumo parcial: um pai LIMIT pode parar o filho antes de este produzir a população completa estimada. Compara grandezas que descrevam o mesmo âmbito.
Encontrar a primeira divergência relevante
Se um nó totalmente consumido e executado uma vez estima 20 linhas mas produz 2000, investiga a subestimação antes de forçar um método de join. Estatísticas desatualizadas, distribuição assimétrica e condições correlacionadas são hipóteses diferentes. ANALYZE recolhe estatísticas; não garante estimativas perfeitas nem deve ser confundido com a opção EXPLAIN ANALYZE. Estatísticas estendidas podem ajudar certas relações entre colunas, mas não resolvem qualquer query automaticamente. Compara parâmetros pequenos e grandes. Uma query preparada pode usar um plano genérico ou customizado, e a escolha útil pode variar com os valores. Evita transformar um caso lento numa alteração global antes de compreender o impacto nos outros workloads.
Índices seguem padrões de acesso
Um B-tree com tenant_id,state,created_at é um candidato para igualdades nas duas primeiras colunas e um intervalo na terceira. Isso é uma hipótese de desenho, não garantia de escolha pelo planner. Em PostgreSQL 18, skip scan pode tornar útil um índice composto mesmo sem condição na primeira coluna quando a distribuição permite pesquisas repetidas vantajosas. Um índice parcial exige que a query implique o seu predicado; um plano genérico com state=$1 não pode assumir sempre state igual a failed. Uma pesquisa lower(email) pode justificar um índice dessa expressão, mantendo a semântica e a collation. Cada índice acrescenta armazenamento e manutenção nas escritas, pelo que se avalia o workload completo.
Cobertura, visibilidade e ordenação
INCLUDE guarda colunas como payload para ajudar a cobrir uma query; não as transforma em chaves de pesquisa ordenadas. Mesmo num Index Only Scan, o motor pode visitar o heap para verificar visibilidade quando a informação necessária não permite evitá-lo. Heap Fetches não significa automaticamente leituras físicas do disco, porque páginas podem estar em cache. Noutro plano, combinar índices por bitmap pode melhorar a localização de linhas mas perde a ordenação lógica dos índices originais. A query continua a precisar de ORDER BY quando a ordem faz parte do contrato. Um Seq Scan numa tabela pequena ou numa seleção ampla pode ser adequado. O nome do nó isolado não determina a qualidade da execução.
Medir sem confundir rollback com ausência de efeitos
EXPLAIN sem ANALYZE mostra o plano estimado sem executar a instrução analisada. EXPLAIN ANALYZE executa-a: um DELETE pode eliminar linhas e uma função chamada pode produzir efeitos. Envolver um ensaio em BEGIN e ROLLBACK pode reverter alterações transacionais, mas não justifica prometer que nada aconteceu. Em PostgreSQL, valores obtidos por nextval não são recuperados pelo rollback. Identifica funções, triggers, sequências e integrações antes de escolher o ambiente do ensaio. Para diagnosticar apenas o plano de uma escrita, começa pela forma sem ANALYZE. A versão JSON muda o formato de saída; não desativa a execução. Os dados desta aula são sintéticos e não são resultados medidos num servidor PostgreSQL.
Laboratório: resultado antes e depois do índice
O código cria seis tarefas sintéticas, seleciona falhas do tenant A e repete a mesma query depois de criar um índice composto. Prevê os identificadores 2 e 4 nas duas leituras. A verificação executada localmente em SQLite confirma que este exemplo preserva o resultado, sem provar um ganho de desempenho PostgreSQL. Num laboratório PostgreSQL próprio, recolhe os planos antes e depois com a mesma população e parâmetros; não imponhas um nome de nó como resultado universal do teste. Para praticar leitura sem servidor, usa o excerto sintético de quatro linhas por 250 loops e calcula o total. Regista separadamente provas de resultado, interpretação e tempo medido.
Resumo para suporte e planeamento de capacidade
Num incidente de lentidão, conserva query, parâmetros, plano, população, métricas e contexto de execução numa evidência reprodutível. Se Sort escreve temporários para disco, testa a hipótese de memória com limites de sessão e concorrência conhecidos. work_mem não é um orçamento único para todo o servidor: várias operações e sessões podem multiplicar o consumo, e outros componentes têm limites próprios. Uma execução rápida isolada não valida uma alteração global. Relaciona esta aula com observabilidade, paginação e transações: reduzir o trabalho errado não melhora a correção e aumentar recursos não resolve todos os planos. A decisão final deve preservar o resultado e demonstrar benefício nas condições relevantes, incluindo o custo de manutenção dos índices.
CREATE TABLE tasks (
id INTEGER PRIMARY KEY,
tenant_id TEXT NOT NULL,
state TEXT NOT NULL,
created_at INTEGER NOT NULL
);
INSERT INTO tasks VALUES
(1,'A','done',10), (2,'A','failed',11),
(3,'B','failed',12), (4,'A','failed',13),
(5,'A','done',14), (6,'A','failed',20);
SELECT id FROM tasks
WHERE tenant_id='A' AND state='failed'
AND created_at>=10 AND created_at<15
ORDER BY created_at,id;
CREATE INDEX tasks_lookup ON tasks(tenant_id,state,created_at);
SELECT id FROM tasks
WHERE tenant_id='A' AND state='failed'
AND created_at>=10 AND created_at<15
ORDER BY created_at,id;Um nó emite quatro linhas em cada um de 250 loops: são 1000 emissões. O cálculo é sintético e não mede um servidor.
Armadilhas comuns
Não tratar custo como milissegundos, somar custos inclusivos, ignorar loops, prometer scans sem heap ou usar LIMIT para esconder trabalho exigido.
Tópicos relacionados: Índices e planos de execução · Integridade, identidade e conflitos de escrita · Paginação e consistência dos resultados
O índice é uma hipótese: conserva a semântica, interpreta o plano e mede benefício e custo no workload real.
Referência: Reading estimates and execution plans · PostgreSQL 18 reference semantics; DR SQL 2026.4; synthetic plan metrics and portable SQLite 3.51.2 examples