O Planejador de Consultas
O planejador de consultas do PostgreSQL, às vezes chamado de otimizador, é o componente que decide como uma consulta será realmente executada antes que uma única linha seja lida.
Ele não segue um conjunto fixo de regras como um interpretador ingênuo faria; em vez disso, ele estima o custo de muitas estratégias de execução candidatas e escolhe a que ele acredita ser a mais barata, razão pela qual o PostgreSQL é descrito como um otimizador baseado em custo.
Tudo o que este planejador decide, desde o tipo de varredura até a ordem de junção e o algoritmo de junção, em última análise remonta a uma única entrada: quantas linhas ele espera em cada etapa, e essa expectativa vem de estatísticas coletadas sobre seus dados.
Resumo
- O planejador estima o custo de estratégias de execução candidatas usando estatísticas de contagem de linhas e escolhe a mais barata, em vez de seguir regras fixas.
- Por que Importa: Um planejador que trabalha com estatísticas precisas escolhe bons planos automaticamente; um planejador que trabalha com estatísticas desatualizadas ou ingênuas escolhe planos ruins, muitas vezes silenciosamente.
- Conceitos Chave: seletividade, estimativa de cardinalidade, modelo de custo,
pg_stats,ANALYZE, GUCs do planejador. - Quando Usar: Raciocinar sobre por que uma consulta escolheu um plano em vez de outro, ajustar
work_memou constantes de custo, ou diagnosticar má estimação sistemática em muitas consultas. - Limitações / Trade-offs: O modelo de custo do planejador é uma aproximação calibrada com constantes configuráveis e assume independência de colunas, a menos que você o informe de outra forma através de estatísticas estendidas.
- Tópicos Relacionados: Leitura de planos
EXPLAIN, design de índices, autovacuum e autoanalyze, estatísticas estendidas.
Fundamentos
Toda consulta que o planejador considera começa com uma pergunta: quantas linhas sobreviverão a este filtro, esta junção, esta agregação?
Esse número é chamado de estimativa de cardinalidade e é o valor mais consequente que o planejador produz, pois todos os cálculos de custo subsequentes dependem dele.
As estimativas de cardinalidade vêm da seletividade, a fração das linhas de uma tabela que se espera que correspondam a uma condição, que o planejador computa usando estatísticas armazenadas na view de sistema pg_stats.
Essas estatísticas não são derivadas varrendo a tabela inteira a cada consulta; elas são capturadas periodicamente pelo comando ANALYZE, que amostra um subconjunto de linhas e constrói um resumo estatístico compacto.
Esse resumo normalmente inclui uma lista dos valores mais comuns e suas frequências, um histograma da distribuição dos valores restantes, uma contagem estimada de valores distintos e a fração de linhas que são nulas.
Para um filtro de igualdade como status = 'shipped', o planejador verifica se 'shipped' aparece na lista de valores mais comuns e usa sua frequência registrada diretamente, se disponível, ou recorre a uma estimativa genérica baseada na contagem de valores distintos, se não.
Para um filtro de intervalo como created_at > now() - interval '7 days', o planejador consulta o histograma para estimar qual fração de linhas cai acima desse limite.
Uma vez que o planejador tem uma estimativa de linha para uma varredura de tabela base, ele alimenta esse número em uma fórmula de custo que leva em conta leituras de página e trabalho de CPU por linha, e esse custo resultante é o que você vê impresso em cada nó na saída de EXPLAIN.
O planejador não é onisciente sobre a intenção da sua consulta; ele só é tão bom quanto o modelo estatístico que lhe é fornecido, e esse modelo é uma aproximação deliberadamente compacta, não uma cópia completa dos seus dados.
Mecânica e Interações
A verdadeira complexidade do planejador emerge quando uma consulta combina múltiplas condições ou múltiplas tabelas, pois as seletividades precisam ser combinadas em uma única estimativa conjunta.
A suposição padrão é a independência de colunas: se status = 'shipped' corresponde a 5% das linhas e country = 'US' corresponde a 20% das linhas, o planejador assume que sua combinação corresponde a aproximadamente 1% das linhas por simples multiplicação.
Essa suposição de independência muitas vezes está errada em esquemas reais, onde colunas como country e currency, ou status e shipped_at IS NULL, são naturalmente correlacionadas, e multiplicar suas seletividades subestima ou superestima a verdadeira seletividade conjunta.
Estatísticas estendidas, criadas com CREATE STATISTICS, existem especificamente para corrigir isso, registrando correlação real observada, dependência ou contagens conjuntas de valores distintos entre colunas nomeadas.
Para junções, o planejador estende a mesma abordagem de estimar-then-cost para cada ordem de junção e algoritmo de junção viáveis que ele está disposto a considerar, usando programação dinâmica para evitar a força bruta de todas as permutações quando o número de tabelas cresce.
Ele pondera uma junção de loop aninhado, que repete uma varredura interna para cada linha externa, contra uma junção de hash, que constrói uma tabela de hash em memória uma vez, contra uma junção de merge, que requer que ambos os lados sejam pré-ordenados pela chave de junção, e cada uma tem uma forma de custo diferente dependendo das contagens de linha estimadas em cada lado.
É precisamente aqui que uma estimativa de cardinalidade ruim se torna perigosa em vez de meramente inconveniente: uma contagem de linha subestimada pode levar o planejador a escolher um loop aninhado que ele acredita que iterará apenas algumas vezes, quando na realidade ele itera centenas de milhares.
Vários parâmetros de configuração, coletivamente chamados de GUCs do planejador, moldam essa decisão sem tocar nas estatísticas em si; random_page_cost ajusta o quão cara a E/S aleatória parece em relação à E/S sequencial, e work_mem limita quanta memória uma operação de ordenação ou hash pode usar antes de despejar em disco.
O worker de autoanalyze do Autovacuum mantém as estatísticas atualizadas automaticamente, acionando um ANALYZE fresco assim que linhas suficientes em uma tabela mudam, embora cargas em massa e exclusões em massa possam ultrapassar esse limite e deixar o planejador trabalhando com uma imagem desatualizada nesse ínterim.
O planejador também trata subconsultas, Common Table Expressions (CTEs) e views em grande parte in-lineando e re-otimizando-as em contexto, em vez de tratá-las como caixas pretas opacas, razão pela qual o plano de um CTE pode parecer diferente dependendo de como ele é referenciado em outro lugar na consulta.
-- A suposição de seletividade conjunta em miniatura: sem estatísticas estendidas,
-- o planejador multiplica essas duas seletividades como se fossem independentes
-- mesmo que os pedidos 'US' sejam desproporcionalmente 'shipped'.
CREATE STATISTICS orders_country_status_stats (dependencies)
ON country, status FROM orders;
ANALYZE orders;Considerações Avançadas e Aplicações
Em escala, o comportamento do planejador torna-se um problema de sistema, não apenas uma curiosidade por consulta, pois milhares de consultas por segundo dependem do mesmo modelo estatístico se mantendo atualizado.
Tabelas particionadas complicam ainda mais a estimativa, pois o planejador deve combinar decisões de poda de partição com estatísticas por partição, e uma partição com dados incomumente distorcidos pode dominar silenciosamente o custo de uma consulta que toca todo o conjunto particionado.
Índices multicolunados também interagem com as estimativas do planejador, pois um índice pode tornar um plano barato o suficiente para ser selecionado mesmo quando a estimativa de linha subjacente está apenas aproximadamente correta, desde que o índice evite uma varredura completa das linhas descartadas.
Declarações preparadas introduzem um modo de planejamento distinto: o PostgreSQL pode planejar genericamente, sem conhecimento dos valores reais dos parâmetros, após um número suficiente de execuções com um plano personalizado, trocando uma pequena quantidade de precisão por plano pela capacidade de pular o replanejamento a cada chamada.
Cargas de trabalho baseadas em extensões adicionam suas próprias peculiaridades estatísticas; os índices de vizinhos mais próximos aproximados do pgvector, por exemplo, dependem do planejador estimar corretamente o custo de uma varredura de índice em comparação com uma varredura sequencial para busca de similaridade, o que depende de estimativas de linha precisas tanto quanto as varreduras B-tree comuns.
Má estimação sistemática em todo um esquema, em vez de uma única consulta, geralmente é um sinal de um problema estrutural: uma tabela onde os limites do autoanalyze são muito grosseiros para seu volume de escrita, ou uma carga de trabalho construída inteiramente em predicados de expressão que estatísticas de coluna simples não conseguem descrever.
| Abordagem | Força | Fraqueza | Melhor Ajuste |
|---|---|---|---|
| Estatísticas padrão de coluna única | Configuração zero, automática via autoanalyze | Assume independência de colunas | A maioria das tabelas com colunas de filtro não relacionadas |
Maior default_statistics_target / SET STATISTICS por coluna | Detalhe de histograma e MCV mais fino | ANALYZE mais lento, linhas de catálogo maiores | Colunas fortemente distorcidas usadas em filtros frequentes |
Estatísticas estendidas (dependencies, ndistinct, mcv) | Modela correlação real de colunas | Deve ser explicitamente criado e reanalisado | Pares de colunas correlacionadas conhecidas causando estimativas conjuntas ruins |
Ajuste manual de GUCs do planejador (random_page_cost, work_mem) | Muda o modelo de custo para corresponder ao hardware real | Efeito global ou por função, não específico da consulta | Armazenamento baseado em SSD ou operações de ordenação/hash com restrição de memória |
Equívocos Comuns
- "O planejador varre a tabela para saber quantas linhas correspondem." - Ele nunca faz isso no tempo de planejamento; ele estima a partir de um resumo estatístico amostrado periodicamente em
pg_stats. - "Mais índices sempre significam melhores planos." - Um índice só ajuda se o modelo de custo do planejador, impulsionado por estimativas de linha precisas, o julgar mais barato do que as alternativas para essa consulta específica.
- "O planejador trata cada condição da cláusula WHERE de forma independente e correta." - Ele assume independência por padrão, o que subestima ou superestima a seletividade sempre que as colunas são correlacionadas.
- "ANALYZE lê a tabela inteira." -
ANALYZEamostra um número limitado de linhas controlado pordefault_statistics_target, não a tabela inteira, o que o mantém rápido mesmo em relações muito grandes. - "Uma consulta lenta sempre significa estatísticas ausentes." - Às vezes, as estatísticas estão corretas e o plano é genuinamente o mais barato disponível; a correção real é uma mudança de esquema ou índice, não
ANALYZE. - "GUCs do planejador como
random_page_costcorrigem estimativas ruins." - GUCs remodelam o modelo de custo aplicado a uma determinada estimativa de linha; eles não fazem nada para corrigir uma estimativa que está simplesmente errada.
FAQs
O que significa que o PostgreSQL usa um planejador "baseado em custo"?
O planejador atribui um custo numérico a cada maneira candidata de executar uma consulta e escolhe a menor, em vez de seguir um conjunto fixo de regras.
Esse custo é derivado de estimativas de contagem de linhas e constantes de custo configuráveis, não de executar as alternativas.
De onde vêm as estimativas de linha do planejador?
Elas vêm de pg_stats, uma view de catálogo preenchida pelo comando ANALYZE.
ANALYZE amostra a tabela em vez de varrê-la completamente, então registra os valores mais comuns, um histograma, contagens de valores distintos e a fração de nulos por coluna.
Por que duas consultas muito semelhantes teriam planos muito diferentes?
Diferentes valores literais podem atingir partes diferentes das estatísticas de uma coluna, como um valor mais comum versus um bucket de histograma, produzindo diferentes estimativas de seletividade.
O planejamento genérico de prepared statements também pode divergir de um plano personalizado construído para um conjunto específico de parâmetros.
Como o planejador decide entre um loop aninhado, hash join e merge join?
Ele estima o custo de cada algoritmo usando as contagens de linha estimadas em ambos os lados da junção e escolhe o mais barato.
Loops aninhados favorecem um lado externo pequeno, junções de hash favorecem um lado não ordenado, mas que caiba na memória, e junções de merge favorecem entradas que já estão ordenadas ou são facilmente ordenáveis.
Por que a independência de colunas é considerada uma "suposição padrão" em vez de uma garantia?
Multiplicar seletividades para combinar múltiplas condições é matematicamente correto apenas quando as colunas são estatisticamente independentes.
Esquemas reais frequentemente têm colunas correlacionadas, que é exatamente a lacuna que as estatísticas estendidas foram projetadas para preencher.
O que realmente muda quando eu executo CREATE STATISTICS para estatísticas estendidas?
O PostgreSQL começa a rastrear uma estatística conjunta, como dependência funcional ou contagem combinada de valores distintos, entre as colunas nomeadas.
Essa estatística conjunta é populada na próxima execução de ANALYZE e usada automaticamente sempre que o planejador estima a seletividade para essas colunas em conjunto.
Aumentar `default_statistics_target` sempre melhora a qualidade do plano?
Pode melhorar a precisão para colunas distorcidas, registrando mais valores mais comuns e buckets de histograma mais finos.
Também torna ANALYZE mais lento e aumenta o tamanho do catálogo, portanto, é melhor aplicá-lo seletivamente às colunas que realmente precisam dele.
Com que frequência o quadro estatístico do planejador é atualizado?
O worker de autoanalyze do Autovacuum aciona um ANALYZE fresco assim que a contagem estimada de alterações de uma tabela cruza um limite.
Cargas em massa ou exclusões em massa podem ultrapassar esse limite, razão pela qual um ANALYZE manual após uma grande alteração de dados muitas vezes vale a pena ser feito explicitamente.
Qual é a relação entre o planejador e EXPLAIN?
EXPLAIN é a ferramenta que expõe o plano escolhido pelo planejador e seus custos subjacentes e estimativas de linha para inspeção.
O próprio planejador é executado em cada consulta, independentemente de você olhar ou não para EXPLAIN.
Os GUCs do planejador como `random_page_cost` podem corrigir um plano que escolhe o índice errado?
Eles podem mudar a preferência relativa do modelo de custo entre E/S sequencial e aleatória, o que às vezes influencia uma decisão limite.
Eles não podem corrigir uma estimativa de linha fundamentalmente errada, que é um problema de estatísticas em vez de um problema de constantes de custo.
Por que subconsultas e CTEs às vezes recebem planos diferentes dependendo de como são usados?
O planejador geralmente in-linea e re-otimiza subconsultas e CTEs no contexto da consulta circundante, em vez de tratá-las como caixas pretas fixas.
Isso significa que o mesmo texto de CTE pode ser planejado de forma diferente dependendo de quais predicados e junções o cercam.
O planejador de consultas é a mesma coisa que o executor de consultas?
Não, o planejador decide qual plano executar, e o executor é o componente separado que realmente percorre esse plano e produz linhas.
EXPLAIN sozinho exercita apenas o planejador; EXPLAIN ANALYZE exercita ambos.
Relacionados
- Fundamentos de Estatísticas - os campos
pg_statse a mecânica de histograma referenciados aqui - ANALYZE & Autoanalyze - como e quando as estatísticas são realmente atualizadas
- Estatísticas Estendidas - corrigindo diretamente a suposição de independência de colunas
- GUCs do Planejador - as constantes de custo que moldam a seleção do plano
- Como EXPLAIN Funciona - lendo a árvore de plano para a qual as estimativas desta página alimentam
Versões do Stack: Esta página foi escrita para PostgreSQL 18.4 (linha estável 18, linha de manutenção 17); a arquitetura do planejador baseado em custo e o modelo
pg_statsdescritos aqui são estáveis em versões principais recentes.