Varredura de índice (somente) --> Varredura de índice de bitmap --> Varredura sequencial
Para algumas linhas, vale a pena executar uma verificação de índice. Se páginas de dados suficientes estiverem visíveis para todos (=suficientemente limpas e não muito carga de gravação simultânea) e o índice puder fornecer todos os valores de coluna necessários, será usada uma varredura apenas de índice mais rápida. Com a expectativa de retorno de mais linhas (maior porcentagem da tabela e dependendo da distribuição de dados, frequências de valor e largura da linha), é mais provável encontrar várias linhas em uma página de dados. Em seguida, vale a pena mudar para uma varredura de índice de bitmap. (Ou para combinar vários índices distintos.) Uma vez que uma grande porcentagem de páginas de dados precisa ser visitada de qualquer maneira, é mais barato executar uma varredura sequencial, filtrar linhas excedentes e ignorar completamente a sobrecarga dos índices.
O uso do índice se torna (muito) mais barato e mais provável quando acessar páginas de dados em ordem aleatória não é (muito) mais caro do que acessá-los em ordem sequencial. Esse é o caso ao usar SSD em vez de discos giratórios, ou ainda mais quanto mais é armazenado em cache na RAM - e os respectivos parâmetros de configuração
random_page_cost
e effective_cache_size
são definidos em conformidade. No seu caso, o Postgres alterna para uma varredura sequencial, esperando encontrar
rows=263962
, isso já é 3% de toda a tabela. (Enquanto apenas rows=47935
são realmente encontrados, veja abaixo.) Mais nesta resposta relacionada:
- Consulta PostgreSQL eficiente em carimbo de data/hora usando verificação de índice ou índice de bitmap?
Cuidado ao forçar planos de consulta
Você não pode forçar um determinado método de planejador diretamente no Postgres, mas pode fazer outros métodos parecem extremamente caros para fins de depuração. Consulte Configuração do método do planejador no manual.
SET enable_seqscan = off
(como sugerido em outra resposta) faz isso para varreduras sequenciais. Mas isso é destinado apenas para fins de depuração em sua sessão. não use isso como uma configuração geral na produção, a menos que você saiba exatamente o que está fazendo. Pode forçar planos de consulta ridículos. O manual:
Esses parâmetros de configuração fornecem um método grosseiro de influenciar os planos de consulta escolhidos pelo otimizador de consulta. Se o plano padrão escolhido pelo otimizador para uma consulta específica não for o ideal, umtemporário A solução é usar um desses parâmetros de configuração para forçar o otimizador a escolher um plano diferente. As melhores maneiras de melhorar a qualidade dos planos escolhidos pelo otimizador incluem ajustar as constantes de custo do planejador (consulte a Seção 19.7.2), executandoANALYZE
manualmente, aumentando o valor dodefault_statistics_target
parâmetro de configuração e aumentando a quantidade de estatísticas coletadas para colunas específicas usandoALTER TABLE SET STATISTICS
.
Isso já é a maioria dos conselhos que você precisa.
- Evite que o PostgreSQL às vezes escolha um plano de consulta ruim
Nesse caso específico, o Postgres espera 5 a 6 vezes mais acessos em
email_activities.email_recipient_id
do que são realmente encontrados:
rows=227007
estimado vs.actual ... rows=40789
estimativa derows=263962
vs.actual ... rows=47935
Se você executar essa consulta com frequência, valerá a pena ter
ANALYZE
observe uma amostra maior para obter estatísticas mais precisas sobre a coluna específica. Sua tabela é grande (~ 10 milhões de linhas), então faça isso:ALTER TABLE email_activities ALTER COLUMN email_recipient_id
SET STATISTICS 3000; -- max 10000, default 100
Em seguida,
ANALYZE email_activities;
Medida de último recurso
Em muito raro casos você pode recorrer para forçar um índice com
SET LOCAL enable_seqscan = off
em uma transação separada ou em uma função com seu próprio ambiente. Como:CREATE OR REPLACE FUNCTION f_count_dist_recipients(_email_campaign_id int, _limit int)
RETURNS bigint AS
$func$
SELECT COUNT(DISTINCT a.email_recipient_id)
FROM email_activities a
WHERE a.email_recipient_id IN (
SELECT id
FROM email_recipients
WHERE email_campaign_id = $1
LIMIT $2) -- or consider query below
$func$ LANGUAGE sql VOLATILE COST 100000 SET enable_seqscan = off;
A configuração se aplica apenas ao escopo local da função.
Aviso: Esta é apenas uma prova de conceito. Mesmo essa intervenção manual muito menos radical pode mordê-lo a longo prazo. Cardinalidades, frequências de valor, seu esquema, configurações globais do Postgres, tudo muda com o tempo. Você vai atualizar para uma nova versão do Postgres. O plano de consulta que você força agora pode se tornar uma péssima ideia mais tarde.
E normalmente isso é apenas uma solução para um problema com sua configuração. Melhor encontrá-lo e corrigi-lo.
Consulta alternativa
Faltam informações essenciais na pergunta, mas essa consulta equivalente provavelmente é mais rápida e mais provável de usar um índice em (
email_recipient_id
) - cada vez mais para um LIMIT
maior . SELECT COUNT(*) AS ct
FROM (
SELECT id
FROM email_recipients
WHERE email_campaign_id = 1607
LIMIT 43000
) r
WHERE EXISTS (
SELECT FROM email_activities
WHERE email_recipient_id = r.id);