O PostgreSQL 19 vai deixar você fixar o plano de execução que funcionava
Os módulos pg_plan_advice e pg_stash_advice permitem ler um query plan como texto e reimpô-lo depois, com um mecanismo que avisa quando a dica não é honrada.

Existe uma cena que todo DBA já viveu: uma query que rodava bem por um ano fica lenta da noite para o dia. Nada foi implantado, o volume de dados cresceu um pouco, o ANALYZE rodou e o planner, com toda a razão diante dos novos números, escolheu um plano diferente. O plano antigo era melhor, e você queria ele de volta. É exatamente para esse problema que o PostgreSQL↳PostgreSQL11 conteúdosPostgreSQL via SSL com GolangData · abr 20195 itens legais sobre data types do PostgreSQLData · mar 20195 serviços gratuitos na cloud para bancos de dados PostgresData · fev 2025Ver tudo em Data → 19 traz dois módulos novos, destrinchados por Dimitri Fontaine, major contributor do projeto, em artigo publicado no Planet PostgreSQL a partir de testes na versão 19 Beta 3.
O ponto central: pg_plan_advice lê um plano de execução de volta na forma de uma string e obriga o planner a segui-la depois; pg_stash_advice guarda essas strings indexadas por query id e as aplica automaticamente. Ambos são módulos contrib, carregados via session_preload_libraries e shared_preload_libraries respectivamente (esse último precisa ser carregado assim para sobreviver a um restart).
Lendo o plano de volta como texto
A ideia começa com um EXPLAIN incrementado. Fontaine usa uma query que junta três tabelas de Fórmula 1 e pede ao planner não só o que ele fez, mas que descreva o que fez num formato que ele mesmo consiga reler:
explain (costs off, plan_advice)
select drivers.surname, count(*) as races
from f1db.results
join f1db.races using (raceid)
join f1db.drivers using (driverid)
where races.year = 2017
group by drivers.surname;Abaixo do plano tradicional aparece um bloco novo, o Generated Plan Advice, com quatro linhas descrevendo quatro decisões:
JOIN_ORDER(results races drivers)
HASH_JOIN(races drivers)
SEQ_SCAN(results races drivers)
NO_GATHER(results races drivers)Cada linha nomeia uma escolha: qual tabela dirige o join e em que ordem, qual método de junção usar, como alcançar cada relação e se vai ou não paralelizar. O detalhe de projeto que faz tudo funcionar é o que não está ali: não há custo, não há estimativa de linhas, não há timing. Como observa Fontaine, "advice descreve resultados, não o raciocínio que os produziu". É justamente por não mencionar estatística alguma que a dica sobrevive a uma mudança de estatísticas.
Reaplicando: o round trip é a funcionalidade
Quando você devolve uma string via pg_plan_advice.advice, o planner é obrigado a segui-la. Forçando a query a dirigir a partir de drivers:
set pg_plan_advice.advice = 'JOIN_ORDER(drivers results races)';O plano realmente muda, e a saída passa a carregar dois blocos: Supplied Plan Advice, ecoando o que você pediu com uma anotação / matched /, e Generated Plan Advice, descrevendo o plano que de fato saiu. Esse ciclo (ler a dica de um plano que você gostou, guardá-la e reaplicá-la depois confirmando pelas anotações matched que cada pedaço pegou) é o coração do recurso.
Um ganho lateral interessante aparece nesse exemplo: ao forçar drivers como tabela dirigente, o planner conseguiu empurrar um Partial HashAggregate para baixo do join (o enable_eager_aggregate, ligado por padrão no 19), reduzindo linhas antes da junção. Forçar results a dirigir não deixava espaço para isso; forçar drivers, sim.
Quando a dica não vence
Esta é, na leitura deste que escreve, a parte mais importante da interface, e a mais fácil de ignorar. Advice restringe a escolha do planner entre os planos que ele consideraria. Não ressuscita planos que foram tirados da mesa. Desligando hash join e pedindo um mesmo assim:
Supplied Plan Advice:
JOIN_ORDER(results races drivers) /* matched */
HASH_JOIN(races) /* matched, failed */O matched, failed diz que a dica foi entendida, aplicou-se à parte certa da query e ainda assim o planner não conseguiu honrá-la. Uma dica que silenciosamente não faz nada seria pior que nenhuma: você carregaria uma string na configuração por dois anos acreditando que ela segurava um plano. Aqui dá para conferir. Para quem faz tuning em produção, essa telemetria de fracasso vale mais que a própria imposição.
Aplicando por query id, sem tocar na aplicação
Setar pg_plan_advice.advice na mão serve para experimentar, mas você não pede isso à aplicação. É aí que entra o pg_stash_advice: ele mapeia query ids para strings de advice em memória compartilhada e as aplica a qualquer query cujo id bata.
O id vem do pg_stat_statements, que é onde você já estava olhando quando notou a query lenta. O fluxo é criar a stash, registrar a dica para o query id e ligar o nome da stash na sessão:
select pg_create_advice_stash('production');
select pg_set_stashed_advice(
'production', -5243066567089054587,
'JOIN_ORDER(drivers results races)'
);
-- depois, na aplicação:
set pg_stash_advice.stash_name = 'production';A partir daí a aplicação não muda nada: nenhuma string de advice na query, nenhum LOAD, nenhum rewrite. O plano muda porque a stash casou com o query id. Fontaine destaca que pg_stat_statements e pg_plan_advice calculam query ids da mesma forma, então um pode nomear o que o outro viu.
O aviso que a documentação repete de propósito
Aqui mora a ressalva que separa uso disciplinado de tiro no pé. A capacidade do planner de mudar de ideia conforme os dados mudam é uma funcionalidade, e advice tira isso. Se a distribuição dos dados desloca sob um plano fixado, você recebe o plano antigo aplicado a dados novos, exatamente a falha que o planner existe para evitar. O README é ainda mais direto: uma dica ruim produzindo um plano ruim é "erro do usuário, não defeito do módulo".
A disciplina que torna advice útil é o corte: a string gerada descreve toda decisão, e você quase nunca quer fixar todas. Se o que virou foi a ordem do join, mantenha apenas o JOIN_ORDER(...) e apague o resto, deixando o planner livre em todo o restante. Aplicar advice também custa tempo de planejamento mesmo quando o plano não muda, o que reforça usá-lo por query, não no cluster inteiro.
O que isso substitui, e o que você já pode usar hoje
Quem roda PostgreSQL em escala conhece as alternativas: os hints do pg_hint_plan em comentários de query, a família enable_* por sessão (grosseira demais) ou reescrever a query até o planner concordar, o que não existe quando a query sai de um ORM que você não controla. O que é novo é o round trip: um plano pode ser lido para fora e a mesma string reimposta. Você não escreve hints do zero torcendo para descreverem o plano lembrado; você guarda um plano que mediu.
Há ainda uma nota prática para quem, como a maioria, não vai rodar o 19 tão cedo. A metade de leitura não depende do servidor 19: o texto de um plano toda versão já imprime. A ferramenta sqlfmt reconstrói o mesmo bloco de quatro linhas a partir de um EXPLAIN comum de qualquer versão, com sqlfmt explain advice plan.txt. E ela vai além ao comparar planos: como o formato não carrega custo nem timing, dois runs do mesmo plano produzem saída idêntica e qualquer diferença é diferença real, algo que um diff bruto de dois EXPLAIN (onde toda linha carrega um custo e portanto toda linha difere) não entrega. É o tipo de comparação que vale fazer antes de um upgrade, para responder se o planner mudou de ideia entre o 16 e o 19. Vale lembrar, porém, que a saída do sqlfmt é uma chave de comparação, não uma string round-trippable: não a alimente de volta no pg_plan_advice esperando que aplique.
Fonte: Planet PostgreSQL
Este artigo foi escrito por Roberto Diniz, colunista de banco de dados. Conteúdo produzido por agente de IA da redação iMasters, sob revisão editorial humana. Saiba como produzimos no expediente.














