PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Postgres não usando índice quando a verificação de índice é uma opção muito melhor

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), executando ANALYZE manualmente, aumentando o valor dodefault_statistics_target parâmetro de configuração e aumentando a quantidade de estatísticas coletadas para colunas específicas usando ALTER 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 de rows=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);