O PostgreSQL pode usar um índice para id < 11 e uma varredura sequencial para id > 11 porque a primeira condição corresponde a pouquíssimas linhas, enquanto a segunda corresponde a quase toda a tabela. Ambos os operadores são compatíveis com índices B-tree; o planejador compara os custos estimados.
Este guia ajuda um desenvolvedor que trabalha em um Mac a reproduzir essa escolha com linhas sintéticas de artigos, ler os planos e verificar se a diferença requer atenção. Ele segue a documentação atual recuperada do PostgreSQL 18 e a documentação atual do node-postgres. Confira a versão do seu servidor antes de comparar os detalhes dos planos; os exemplos não garantem planos idênticos em diferentes versões ou máquinas. PostgreSQL: Usando EXPLAIN
Por que o mesmo índice pode levar a escolhas de varredura diferentes?
Um índice B-tree é compatível com comparações menor que e maior que. Mudar a direção da comparação não torna o índice inutilizável. O que muda é o intervalo de valores — e, portanto, o número de linhas — que o planejador espera recuperar. PostgreSQL: Tipos de índice
Considere uma tabela ilustrativa com exatamente 100.000 linhas de artigos e IDs inteiros consecutivos de 1 a 100.000:
| Predicado | Linhas correspondentes | Parcela da tabela |
|---|---|---|
id < 11 |
10 | 0,01% |
id > 11 |
99.989 | 99,989% |
id = 11 |
1 | 0,001% |
Essas contagens decorrem dos dados de entrada sintéticos, não de medições de um banco de dados existente. Nenhum dos intervalos inclui o ID 11. Um índice pode localizar com eficiência os dez IDs correspondentes no primeiro intervalo. No segundo, ele identificaria quase todas as linhas de qualquer forma.
Uma varredura comum de índice do PostgreSQL recupera entradas do índice e as linhas correspondentes do armazenamento principal da tabela, chamado heap. Essas linhas do heap podem estar distribuídas por muitas páginas. Quando quase todas as páginas são necessárias, ler a tabela diretamente pode custar menos do que percorrer o índice e buscar as linhas às quais ele aponta. PostgreSQL: Varreduras somente de índice e índices de cobertura
Portanto, o número 11 não tem nenhum significado especial para o planejador. Com dados diferentes, a condição maior que poderia corresponder ao intervalo estreito. O PostgreSQL estima a fração de linhas correspondentes, conhecida como seletividade, usando estatísticas da tabela. O tamanho numérico do limite, por si só, não determina essa fração. PostgreSQL: Estatísticas usadas pelo planejador
Como reproduzir a comparação em um Mac?
Você precisa de uma conexão existente com o PostgreSQL e do comando psql disponível no Terminal do seu Mac. A instalação não faz parte deste exercício. Abra sua conexão usual com o banco usando psql; ele aceita configurações de conexão e fornece um prompt SQL interativo. Execute os blocos a seguir em uma única sessão nova. PostgreSQL: psql
A configuração cria uma tabela temporária contendo apenas texto sintético de artigos. As tabelas temporárias desaparecem quando a sessão termina. A chave primária inteira comum cria automaticamente um índice B-tree exclusivo, portanto, um segundo índice em id é desnecessário. PostgreSQL: CREATE TABLE
SHOW server_version;
CREATE TEMP TABLE article_scan_demo (
id integer PRIMARY KEY,
title text NOT NULL,
body text NOT NULL
);
INSERT INTO article_scan_demo (id, title, body)
SELECT
g,
'Article ' || g,
repeat('Sample article text. ', 20)
FROM generate_series(1, 100000) AS s(g);
ANALYZE article_scan_demo;
SELECT
count(*) AS total_rows,
count(*) FILTER (WHERE id < 11) AS below_11,
count(*) FILTER (WHERE id > 11) AS above_11
FROM article_scan_demo;
O generate_series inteiro do PostgreSQL inclui os dois extremos com o passo padrão de um. Portanto, a consulta de contagem deve retornar 100.000 linhas no total, 10 abaixo de 11 e 99.989 acima de 11. PostgreSQL: Funções que retornam conjuntos
Execute ANALYZE depois de carregar as linhas para que o planejador tenha estatísticas sobre a tabela preenchida. Esse comando coleta estatísticas; ele tem uma finalidade diferente da opção de medição da execução em EXPLAIN ANALYZE. PostgreSQL: ANALYZE
Em seguida, compare as duas consultas que retornam todas as colunas:
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF)
SELECT * FROM article_scan_demo WHERE id < 11;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF)
SELECT * FROM article_scan_demo WHERE id > 11;
Esses comandos executam cada consulta e informam seu plano e as contagens reais de linhas, sem retornar todas as linhas de artigos selecionadas. BUFFERS adiciona a atividade de blocos; TIMING OFF desativa a medição do tempo de cada nó. Isso é útil para estudar a execução, mas não mede o tempo necessário para enviar todos os dados dos artigos correspondentes a um aplicativo de desktop. PostgreSQL: EXPLAIN
Em condições normais, espere um plano baseado em índice para o intervalo estreito e uma varredura sequencial para o intervalo amplo. Trate isso como uma expectativa: com base em suas estimativas, o PostgreSQL pode escolher um plano de bitmap ou outro plano aplicável. O SQL deste exemplo foi inspecionado, mas não havia um servidor PostgreSQL disponível para executá-lo durante a preparação; a aritmética das contagens de linhas foi verificada separadamente.
Quais partes do plano explicam a decisão?
Comece pelo nó de varredura, seu rows estimado e seu rows real. Para a primeira consulta, procure Index Scan com um Index Cond, ou um Bitmap Index Scan abaixo de um Bitmap Heap Scan. Para a segunda, procure Seq Scan com um Filter. Um plano de bitmap ainda usa um índice; ele organiza as localizações das linhas correspondentes antes de buscar as páginas do heap. PostgreSQL: Usando EXPLAIN
Em seguida, confira estas observações:
| Observação neste exemplo | O que investigar |
|---|---|
| As estimativas correspondem aproximadamente a 10 e 99.989 linhas reais | A diferença na quantidade de linhas correspondentes explica as escolhas distintas. |
| As linhas estimadas diferem bastante das reais | Atualize as estatísticas antes de alterar as configurações do planejador. |
| Ambas as condições usam varreduras sequenciais em uma amostra muito menor | O tamanho da tabela pode tornar a varredura direta mais barata. |
| Aparece um plano de bitmap | Compare as estimativas de linhas e o trabalho no heap como outra alternativa baseada em índice. |
Os custos do planejador são estimativas em unidades arbitrárias, não em milissegundos. Compare-os como parte do modelo do PostgreSQL, sem interpretar um custo de 100 como uma duração. Como o exemplo usa tabelas temporárias, a atividade de buffers aparece em blocos locais; os buffers de tabelas e índices regulares aparecem em blocos compartilhados. PostgreSQL: EXPLAIN
Se as estimativas parecerem surpreendentes, execute esta consulta de diagnóstico na mesma sessão:
SELECT
schemaname,
n_distinct,
histogram_bounds,
correlation
FROM pg_stats
WHERE schemaname = (
SELECT nspname
FROM pg_namespace
WHERE oid = pg_my_temp_schema()
)
AND tablename = 'article_scan_demo'
AND attname = 'id';
O histograma descreve a distribuição de valores usada para estimar intervalos. A correlação descreve o quanto a ordem lógica da coluna acompanha a ordem física das linhas; valores próximos de qualquer um dos extremos podem fazer o acesso ao heap por índice parecer menos custoso. Nenhum desses campos estabelece uma porcentagem universal em que o PostgreSQL precise mudar de tipo de varredura. PostgreSQL: pg_stats
Como comparar as consultas usando node-postgres?
O node-postgres envia o texto da consulta e os valores dos parâmetros ao PostgreSQL, onde o planejamento acontece. Sua API de consultas aceita o SQL seguido de um array de valores, portanto as duas comparações podem usar o mesmo valor limite, 11. node-postgres: Consultas
Para uma reprodução independente com Node.js, use uma instalação funcional do Node.js/npm e as configurações da sua conexão existente com o PostgreSQL. Em um novo diretório de projeto, execute npm init -y e, em seguida, npm install pg. Salve o programa completo abaixo como compare-scans.mjs e execute node compare-scans.mjs. O cliente usa configurações de ambiente do PostgreSQL, como PGHOST, PGPORT e PGDATABASE; use as configurações do seu banco de dados local existente. node-postgres: Conexão
import pg from 'pg';
const client = new pg.Client();
try {
await client.connect();
await client.query(`
CREATE TEMP TABLE article_scan_demo (
id integer PRIMARY KEY,
title text NOT NULL,
body text NOT NULL
)
`);
await client.query(`
INSERT INTO article_scan_demo (id, title, body)
SELECT
g,
'Article ' || g,
repeat('Sample article text. ', 20)
FROM generate_series(1, 100000) AS s(g)
`);
await client.query('ANALYZE article_scan_demo');
const queries = [
'EXPLAIN (ANALYZE, BUFFERS, TIMING OFF) SELECT * FROM article_scan_demo WHERE id < $1',
'EXPLAIN (ANALYZE, BUFFERS, TIMING OFF) SELECT * FROM article_scan_demo WHERE id > $1'
];
for (const text of queries) {
const result = await client.query(text, [11]);
console.log(text);
console.log(
result.rows.map(row => row['QUERY PLAN']).join('\n')
);
}
} finally {
await client.end();
}
O programa cria sua própria tabela de amostra e mantém todas as operações em uma única conexão. Ele imprime os dois planos e fecha a conexão em seguida. A sintaxe JavaScript foi verificada com Node.js; a execução no banco de dados não foi realizada durante a preparação. Se a configuração da conexão falhar, resolva esse problema antes de interpretar o comportamento das varreduras.
Instruções preparadas nomeadas podem mudar a comparação?
Adicionar um name à configuração de uma consulta do node-postgres cria uma instrução preparada nomeada em cada conexão. Isso introduz outra distinção: o PostgreSQL pode usar um plano personalizado com base nos valores dos parâmetros ou um plano genérico compartilhado entre execuções. node-postgres: Consultas, PostgreSQL: PREPARE
Na política automática padrão do PostgreSQL 18, as primeiras cinco execuções usam planos personalizados. Em seguida, o PostgreSQL compara o custo estimado de um plano genérico com o custo médio dos planos personalizados; ele não muda automaticamente para um plano genérico na sexta execução. PostgreSQL: PREPARE
Se uma instrução nomeada de um aplicativo se comportar de modo diferente de um teste com SQL literal, examine essa instrução na mesma conexão usando EXPLAIN EXECUTE. Um plano genérico exibe símbolos de parâmetros, enquanto um plano personalizado substitui os valores fornecidos. Mantenha os dois operadores de comparação em instruções nomeadas separadas, pois o texto SQL é diferente. PostgreSQL: PREPARE
Quando você deve alterar a consulta ou as configurações do planejador?
Primeiro, preserve a tarefa real da consulta. Substituir SELECT * por SELECT id muda quais dados o PostgreSQL precisa recuperar. Quando todas as colunas necessárias estão disponíveis em um índice adequado, torna-se possível uma varredura somente de índice. Evitar buscas no heap também depende do mapa de visibilidade, portanto um plano somente de índice não garante acesso zero ao heap. PostgreSQL: Varreduras somente de índice e índices de cobertura
Se a tarefa exigir todos os artigos correspondentes, mantenha esse requisito ao comparar os planos. Se ela exigir apenas um pequeno subconjunto ordenado, teste a consulta real com ORDER BY e LIMIT: a ordenação e a interrupção antecipada podem mudar o plano preferido. PostgreSQL: Usando EXPLAIN
Depois de conferir as estatísticas, você pode comparar um plano alternativo na sessão SQL existente:
BEGIN;
SET LOCAL enable_seqscan = off;
EXPLAIN (ANALYZE, BUFFERS, TIMING OFF)
SELECT * FROM article_scan_demo WHERE id > 11;
ROLLBACK;
Essa configuração desestimula varreduras sequenciais; ela não consegue suprimi-las por completo. Use o resultado como comparação de diagnóstico. O PostgreSQL também modela o acesso a páginas sequencial e não sequencial por meio de seq_page_cost e random_page_cost. Alterar seus valores relativos pode mudar as escolhas de plano, mas a documentação recomenda avaliar as configurações de custo considerando a carga de consultas como um todo, em vez de apenas alguns experimentos. PostgreSQL: Planejamento de consultas
Para esta tabela sintética, a comparação decisiva é entre 10 linhas correspondentes e 99.989. Confira a consulta exata, atualize as estatísticas, compare linhas estimadas e reais e examine o comportamento dos planos preparados quando isso for relevante. Essas etapas permitem determinar se a varredura sequencial é adequada ao trabalho solicitado e fornecem evidências para qualquer alteração posterior.
Fontes
- Usando EXPLAIN — PostgreSQL
- Tipos de índice — PostgreSQL
- Estatísticas usadas pelo planejador — PostgreSQL
- Varreduras somente de índice e índices de cobertura — PostgreSQL
- psql — PostgreSQL
- CREATE TABLE — PostgreSQL
- Funções que retornam conjuntos — PostgreSQL
- ANALYZE — PostgreSQL
- EXPLAIN — PostgreSQL
- pg_stats — PostgreSQL
- Consultas — node-postgres
- Conexão — node-postgres
- PREPARE — PostgreSQL
- Planejamento de consultas — PostgreSQL