Como faço para forçar o Postgres a usar um índice específico?

111

Como faço para forçar o Postgres a usar um índice quando, de outra forma, ele insistiria em fazer uma varredura sequencial?

mike
fonte
Duplicado, consulte stackoverflow.com/questions/14554302/…
Grigory Kislin
1
1 Eu adoraria ver esse recurso. Não é uma questão de simplesmente desabilitar a varredura de seq, como dizem outras respostas: precisamos da capacidade de forçar o PG a usar um índice específico . Isso ocorre porque, na palavra real, as estatísticas podem estar completamente erradas e, nesse ponto, você precisa usar soluções alternativas não confiáveis ​​/ parciais. Concordo que, em casos simples, você deve primeiro verificar os índices e outras configurações, mas para confiabilidade e usos avançados em big data, precisamos disso.
Collimarco
MySQL e Oracle têm isso ... Não tenho certeza por que o planejador do Postgres é tão confiável.
Kevin Parker

Respostas:

103

Supondo que você esteja perguntando sobre o recurso comum de "sugestão de índice" encontrado em muitos bancos de dados, o PostgreSQL não fornece tal recurso. Esta foi uma decisão consciente feita pela equipe do PostgreSQL. Uma boa visão geral do porquê e do que você pode fazer pode ser encontrada aqui . Os motivos são basicamente que é um hack de desempenho que tende a causar mais problemas posteriormente, conforme os dados mudam, enquanto o otimizador do PostgreSQL pode reavaliar o plano com base nas estatísticas. Em outras palavras, o que pode ser um bom plano de consulta hoje provavelmente não será um bom plano de consulta para sempre, e as dicas de índice forçam um plano de consulta específico para todo o tempo.

Como um martelo muito cego, útil para testes, você pode usar os parâmetros enable_seqscane enable_indexscan. Vejo:

Eles não são adequados para uso em produção contínua . Se você tiver problemas com a escolha do plano de consulta, deverá consultar a documentação para rastrear problemas de desempenho de consulta . Não basta definir enable_parâmetros e ir embora.

A menos que você tenha um bom motivo para usar o índice, o Postgres pode estar fazendo a escolha correta. Por quê?

  • Para tabelas pequenas, é mais rápido fazer varreduras sequenciais.
  • O Postgres não usa índices quando os tipos de dados não correspondem adequadamente, você pode precisar incluir conversões apropriadas.
  • As configurações do planejador podem estar causando problemas.

Veja também esta postagem de um grupo de notícias antigo .

Patryk Kordylewski
fonte
4
Concordo, forçar o postgres a fazer do seu jeito geralmente significa que você fez tudo errado. 9/10 Vezes que o planejador vencerá qualquer coisa que você inventar. A outra vez é porque você errou.
Kent Fredric
Eu acho que é uma boa ideia para verificar realmente as classes de operadores de sua retenção de índice.
metdos
2
Eu odeio reviver uma velha questão, mas vejo frequentemente na documentação do Postgres, discussões e aqui, mas existe um conceito generalizado para o que se qualifica para uma pequena mesa ? É algo como 5000 linhas ou 50000 etc?
waffl de
1
@waffl Você já considerou o benchmarking? Crie uma tabela simples com um índice e uma função de acompanhamento para preenchê-la com n linhas de lixo aleatório. Em seguida, comece a olhar para o plano de consulta para diferentes valores de n . Quando você começar a usar o índice, deverá ter uma resposta aproximada. Você também pode obter varreduras sequenciais se o PostgreSQL determinar (com base nas estatísticas) que uma varredura de índice também não eliminará muitas linhas. Portanto, o benchmarking é sempre uma boa ideia quando você tem preocupações reais com o desempenho. Como uma suposição improvisada e anedótica, eu diria que alguns milhares geralmente são "pequenos".
jpmc26
9
Com mais de 30 anos de experiência em plataformas como Oracle, Teradata e MSSQL, acho o otimizador do PostgreSQL 10 não especialmente inteligente. Mesmo com estatísticas atualizadas, ele gera planos de execução menos eficientes do que forçados em uma direção especial. Fornecer dicas estruturais para compensar esses problemas forneceria uma solução para permitir que o PostgreSQL cresça em mais segmentos de mercado. NA MINHA HUMILDE OPINIÃO.
Guido Leenders,
75

Provavelmente, a única razão válida para usar

set enable_seqscan=false

é quando você está escrevendo consultas e deseja ver rapidamente o que o plano de consulta realmente seria se houvesse grandes quantidades de dados na (s) tabela (s). Ou, claro, se você precisar confirmar rapidamente que sua consulta não está usando um índice simplesmente porque o conjunto de dados é muito pequeno.

Niraj Bhawnani
fonte
41
esta curta resposta na verdade dá uma boa dica para fins de teste
dwery
3
Ninguém está respondendo à pergunta!
Ivailo Bardarov
@IvailoBardarov O motivo de todas essas outras sugestões estarem aqui é porque o PostgreSQL não tem esse recurso; esta foi uma decisão consciente feita pelos desenvolvedores com base em como é normalmente usado e os problemas de longo prazo que causa.
jpmc26
Um bom truque para testar: execute set enable_seqscan=false, execute sua consulta e, em seguida, execute rapidamente set enable_seqscan=truepara retornar o postgresql ao seu comportamento adequado (e obviamente não faça isso na produção, apenas no desenvolvimento!)
Brian Hellekin
2
@BrianHellekin Melhor, SET SESSION enable_seqscan=falsepara afetar apenas a si mesmo
Izkata
19

Às vezes, o PostgreSQL falha em fazer a melhor escolha de índices para uma determinada condição. Por exemplo, suponha que haja uma tabela de transações com vários milhões de linhas, das quais existem várias centenas para qualquer dia, e a tabela tem quatro índices: transaction_id, client_id, date e description. Você deseja executar a seguinte consulta:

SELECT client_id, SUM(amount)
FROM transactions
WHERE date >= 'yesterday'::timestamp AND date < 'today'::timestamp AND
      description = 'Refund'
GROUP BY client_id

O PostgreSQL pode escolher usar o índice transactions_description_idx ao invés de transaction_date_idx, o que pode fazer com que a consulta demore vários minutos em vez de menos de um segundo. Se for este o caso, você pode forçar o uso do índice na data falsificando a condição desta forma:

SELECT client_id, SUM(amount)
FROM transactions
WHERE date >= 'yesterday'::timestamp AND date < 'today'::timestamp AND
      description||'' = 'Refund'
GROUP BY client_id
Ziggy Crueltyfree Zeitgeister
fonte
3
Boa ideia. No entanto, quando desabilitamos o uso do índice atual com este método - o otimizador de consulta postgresql retorna para o próximo índice adequado. Portanto, não há garantia de que o otimizador escolherá your_wanted_index, pode ser que o mecanismo postgresql apenas execute uma varredura de sequência / chave primária. Conclusão - não existe um método 100% confiável para forçar algum uso de índice para o servidor PostgreSql.
Agnius Vasiliauskas
E se não houver nenhuma wherecondição além de duas tabelas ou unidas e o Postgres falhar em obter o índice.
Luna Lovegood
@Surya o acima se aplica às condições WHERE e JOIN ... ON
Ziggy Crueltyfree Zeitgeister
18

Resposta curta

Esse problema normalmente ocorre quando o custo estimado de uma varredura de índice é muito alto e não reflete a realidade corretamente. Você pode precisar diminuir o random_page_costparâmetro de configuração para corrigir isso. Da documentação do Postgres :

Reduzir este valor [...] fará com que o sistema dê preferência às varreduras de índice; aumentá-lo fará com que as varreduras de índice pareçam relativamente mais caras.

Você pode verificar se um valor menor realmente fará o Postgres usar o índice (mas use-o apenas para teste ):

EXPLAIN <query>;              # Uses sequential scan
SET random_page_cost = 1;
EXPLAIN <query>;              # May use index scan now

Você pode restaurar o valor padrão com SET random_page_cost = DEFAULT;novamente.

fundo

As varreduras de índice requerem buscas de páginas de disco não sequenciais. Postgres usa random_page_costpara estimar o custo de tais buscas não sequenciais em relação às buscas sequenciais. O valor padrão é 4.0, assumindo assim um fator de custo médio de 4 em comparação com as buscas sequenciais (levando em consideração os efeitos do cache).

O problema, entretanto, é que esse valor padrão não é adequado nos seguintes cenários importantes da vida real:

1) Drives de estado sólido

Como a documentação admite:

O armazenamento que tem um baixo custo de leitura aleatória em relação ao sequencial, por exemplo, unidades de estado sólido, pode ser melhor modelado com um valor mais baixo para random_page_cost.

De acordo com o último ponto deste slide de uma palestra no PostgresConf 2018, random_page_costdeve ser definido como algo entre 1.0e 2.0para drives de estado sólido.

2) Dados em cache

Se os dados de índice necessários já estiverem armazenados em cache na RAM, uma varredura de índice sempre será significativamente mais rápida do que uma varredura sequencial. A documentação diz:

Da mesma forma, se é provável que seus dados estejam completamente no cache, [...] diminuir random_page_costpode ser apropriado.

O problema é que você não pode saber facilmente se os dados relevantes já estão armazenados em cache. No entanto, se um índice específico for consultado com frequência e se o sistema tiver RAM suficiente, os dados provavelmente serão armazenados em cache e random_page_costdeverão ser configurados com um valor inferior. Você terá que experimentar diferentes valores e ver o que funciona para você.

Você também pode querer usar a extensão pg_prewarm para armazenamento de dados explícito.


emkey08
fonte
2
Eu até tive que definir random_page_cost = 0.1 para fazer a varredura de índice funcionar em grande (tabela de ~ 600 milhões de linhas) na Pg 10.1 no Ubuntu. Sem o ajuste, a varredura de sequência (apesar de ser paralela) levava 12 minutos (observe que a tabela de análise foi realizada!). A unidade é SSD. Após o ajuste, o tempo de execução passou a ser de 1 segundo.
Anatoly Alekseev
Você salvou meu dia. Eu estava ficando louco tentando descobrir como exatamente a mesma consulta no mesmo banco de dados estava levando 30 segundos em uma máquina e menos de 1 em outra, mesmo depois de executar a análise em ambas as extremidades ... A quem possa interessar: o comando ' ALTER SYSTEM SET random_page_cost = x 'define o novo valor padrão globalmente.
Julien
10

A pergunta em si é muito inválida. Forçar (fazendo enable_seqscan = off por exemplo) é uma ideia muito ruim. Pode ser útil verificar se será mais rápido, mas o código de produção nunca deve usar esses truques.

Em vez disso - explique a análise de sua consulta, leia-a e descubra por que o PostgreSQL escolheu um plano ruim (em sua opinião).

Existem ferramentas na web que ajudam a ler a saída de explicação e análise - uma delas é explain.depesz.com - escrita por mim.

Outra opção é entrar no canal #postgresql na rede freenode irc, e falar com os caras lá para ajudá-lo - pois otimizar a consulta não é uma questão de "fazer uma pergunta, obter uma resposta ser feliz". é mais como uma conversa, com muitas coisas para checar, muitas coisas para aprender.

user80168
fonte
2

Existe um truque para empurrar o postgres para preferir um seqscan adicionando um OFFSET 0na subconsulta

Isso é útil para otimizar solicitações que ligam tabelas grandes / enormes quando tudo o que você precisa são apenas os n primeiros / últimos elementos.

Digamos que você esteja procurando pelos primeiros / últimos 20 elementos envolvendo várias tabelas com 100k (ou mais) entradas, não adianta construir / vincular todas as consultas sobre todos os dados quando o que você está procurando está nos primeiros 100 ou 1000 entradas. Neste cenário, por exemplo, é 10x mais rápido fazer uma varredura sequencial.

veja Como posso evitar que o Postgres inlining uma subconsulta?

Antony Gibbs
fonte
Belo truque. Embora um bom otimizador deva obviamente otimizar o deslocamento 0 :-)
Guido Leenders,