Como usar o pg_stat_statements para achar as queries mais caras em produção
No sétimo e último episódio da série Postgres in Production, Ryan Booz mostra por onde começar quando o chamado de lentidão chega e você não tem histórico de métricas guardado.

O pg_stat_statements é provavelmente a extensão mais citada quando o assunto é performance no 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 →, mas boa parte de quem a habilita nunca chegou a consultá-la de forma estruturada durante um incidente real. É exatamente essa lacuna que Ryan Booz, do time da pganalyze, fecha no sétimo e último episódio da série Postgres in Production (fonte). Depois de seis episódios explicando o que a extensão é, como ela normaliza texto de query e onde armazena esse texto, chegou a hora de rodar consultas de verdade contra a view.
O ponto de partida do raciocínio de Booz é incômodo, mas correto: pg_stat_statements não tem timeline nenhuma. Os números são cumulativos desde o último reset. Se você quer enxergar tendência, precisa de snapshots, cálculo de delta entre eles e um lugar para guardar esse histórico fora da própria view, lidando com resets, queries despejadas do cache e queries novas entrando no meio do caminho. Isso é, em essência, o trabalho que qualquer ferramenta de monitoramento faz por você.
Por que consultar pg_stat_activity antes
O primeiro instinto de quem aprende sobre pg_stat_statements durante um incidente costuma ser errado: ir direto para a extensão. Ela só registra métricas de uma query depois que a execução termina. Se o problema agora é uma consulta ad hoc travada, ainda em execução, ela simplesmente não vai aparecer ali (ou vai aparecer com dados de execuções anteriores, que não refletem o que está acontecendo neste segundo). Por isso o roteiro correto começa em pg_stat_activity:
SELECT
pid,
query_id,
usename,
application_name,
state,
now() - xact_start AS transaction_duration,
now() - query_start AS query_duration,
wait_event_type,
wait_event,
query
FROM pg_stat_activity
WHERE state = 'active'
AND pid <> pg_backend_pid()
ORDER BY query_duration DESC;Essa consulta é um teste de sanidade: existe algo anormal rodando agora, fora do padrão de replicação e das rotinas normais? Se sim, o problema pode nem ser de pg_stat_statements, e sim uma transação travada, um lock ou uma query ad hoc mal escrita que ninguém normalizou ainda.
Duas formas de tirar uma janela de dados cumulativos
Descartada a hipótese de algo acontecendo em tempo real, é hora de ir atrás de padrões: uma query rodando repetidamente, um processo martelando o mesmo statement. Booz descreve duas abordagens.
A abordagem segura é tirar dois snapshots com um intervalo de tempo entre eles (10 segundos, 30 segundos, um minuto, o que fizer sentido para o seu workload) e calcular a diferença:
CREATE TEMP TABLE pgss_before AS
SELECT * FROM pg_stat_statements;
-- espere o tempo suficiente para o workload se repetir
CREATE TEMP TABLE pgss_after AS
SELECT * FROM pg_stat_statements;
SELECT
a.queryid,
a.calls - b.calls AS calls_delta,
round((a.total_exec_time - b.total_exec_time)::numeric, 2) AS exec_time_delta_ms,
a.rows - b.rows AS rows_delta,
a.shared_blks_read - b.shared_blks_read AS shared_reads_delta,
a.temp_blks_written - b.temp_blks_written AS temp_written_delta,
left(a.query, 100) AS query
FROM pgss_after a
JOIN pgss_before b
ON a.userid = b.userid AND a.dbid = b.dbid AND a.queryid = b.queryid
WHERE a.calls > b.calls
ORDER BY exec_time_delta_ms DESC
LIMIT 10;Essa é a opção que eu recomendaria para um ambiente de produção sensível, porque não descarta nada: você só está olhando para um recorte temporal sem apagar o histórico acumulado que já existia.
Já a abordagem agressiva é resetar tudo com SELECT pg_stat_statements_reset(); e voltar a consultar a partir de uma tabela limpa. Ela cria um intervalo de observação sem ruído acumulado, o que ajuda muito quando o incidente é ativo e repetível. Mas tem custo: se o problema for raro, ou se o histórico anterior importa (para comparar antes/depois de uma mudança, por exemplo), resetar joga isso fora. Para saber quando o último reset aconteceu, vale checar pg_stat_statements_info:
SELECT dealloc, stats_reset FROM pg_stat_statements_info;O ORDER BY certo depende da pergunta, não da tabela
A parte mais prática do episódio é lembrar que a mesma consulta, só trocando o ORDER BY, responde perguntas diferentes:
-- o que mais executa?
ORDER BY calls DESC
-- o que é lento toda vez que roda?
WHERE calls >= 10
ORDER BY mean_exec_time DESC
-- o que lê mais dado do disco?
ORDER BY shared_blks_read DESC
-- o que derrama pra disco (temp files)?
ORDER BY temp_blks_written DESCO alerta de Booz aqui é o que mais vale reter: a query mais lenta em média nem sempre é o seu problema real. Uma consulta de 4ms chamada 2 milhões de vezes consome 8.000 segundos de tempo total; uma consulta de 4 segundos chamada 20 vezes consome 80 segundos. A primeira parece inofensiva olhando só o mean_exec_time, mas é ela que está sugando CPU e I/O do servidor. total_exec_time captura esse efeito de
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.














