Índice que não é usado, mas influencia a consulta

8

Eu tenho uma tabela do PostgreSQL 9.3 com alguns números e alguns dados adicionais:

CREATE TABLE mytable (
    myid BIGINT,
    somedata BYTEA
)

Atualmente, esta tabela possui cerca de 10 milhões de registros e ocupa 1 GB de espaço em disco. myidnão são consecutivos.

Quero calcular quantas linhas existem em cada bloco de 100000 números consecutivos:

SELECT myid/100000 AS block, count(*) AS total FROM mytable GROUP BY myid/100000;

Isso retorna cerca de 3500 linhas.

Percebi que a existência de um determinado índice acelera significativamente essa consulta, embora o plano de consulta não a mencione. O plano de consulta sem o índice:

db=> EXPLAIN (ANALYZE TRUE, VERBOSE TRUE) SELECT myid/100000 AS block, count(*) AS total FROM mytable GROUP BY myid/100000;
                                                               QUERY PLAN                                                               
----------------------------------------------------------------------------------------------------------------------------------------
 GroupAggregate  (cost=1636639.92..1709958.65 rows=496942 width=8) (actual time=6783.763..8888.841 rows=3460 loops=1)
   Output: ((myid / 100000)), count(*)
   ->  Sort  (cost=1636639.92..1659008.91 rows=8947594 width=8) (actual time=6783.752..8005.831 rows=8947557 loops=1)
         Output: ((myid / 100000))
         Sort Key: ((mytable.myid / 100000))
         Sort Method: external merge  Disk: 157440kB
         ->  Seq Scan on public.mytable  (cost=0.00..236506.92 rows=8947594 width=8) (actual time=0.020..1674.838 rows=8947557 loops=1)
               Output: (myid / 100000)
 Total runtime: 8914.780 ms
(9 rows)

O índice:

db=> CREATE INDEX myindex ON mytable ((myid/100000));
db=> VACUUM ANALYZE;

O novo plano de consulta:

db=> EXPLAIN (ANALYZE TRUE, VERBOSE TRUE) SELECT myid/100000 AS block, count(*) AS total FROM mytable GROUP BY myid/100000;
                                                            QUERY PLAN                                                            
----------------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=281242.99..281285.97 rows=3439 width=8) (actual time=3190.189..3190.800 rows=3460 loops=1)
   Output: ((myid / 100000)), count(*)
   ->  Seq Scan on public.mytable  (cost=0.00..236505.56 rows=8947485 width=8) (actual time=0.026..1659.571 rows=8947557 loops=1)
         Output: (myid / 100000)
 Total runtime: 3190.975 ms
(5 rows)

Portanto, os planos de consulta e os tempos de execução diferem significativamente (quase três vezes), mas nenhum deles menciona o índice. Esse comportamento é perfeitamente reproduzível na minha máquina de desenvolvimento: passei por vários ciclos de eliminação do índice, testando a consulta várias vezes, recriando o índice, testando novamente a consulta várias vezes. O que está acontecendo aqui?

liori
fonte
Não sou especialista em analisar os planos de consulta do Postgres, mas acho que o índice é usado para o HashAggregatemétodo (e nenhuma classificação é necessária), para que você obtenha melhor desempenho. Por que o índice não é mencionado no plano, não faço ideia.
ypercubeᵀᴹ
A saída do plano muda se você ativar o modo detalhado usando explain (analyze true, verbose true) ...:?
A_horse_with_no_name 29/07
Seria ótimo se você pudesse resumir este em um caso de teste independente. Com certeza parece estranho.
Craig Ringer
@a_horse_with_no_name: Sim, ele muda - substituí os planos de consulta pelos detalhados da pergunta. Mas esse plano de consulta ainda não menciona o índice.
Liori 29/07
Se houver mais estatísticas disponíveis (especialmente cardinalidade e possivelmente valores mín. / Máx.) Na coluna id com o índice do que sem, isso poderá alterar o grupo do otimizador pela seleção do método, mesmo que ele não acabe usando o índice . (Eu não conheço o otimizador e as estatísticas do postgres, então não faço ideia se isso pode ser o caso ou não.) #
315

Respostas:

3

VACUUM ANALYZEfaz a diferença no seu exemplo. Além disso, como o @jjanes forneceu , as estatísticas adicionais para o índice funcional. Por documentação:

pg_statistictambém armazena dados estatísticos sobre os valores das expressões de índice. Eles são descritos como se fossem colunas de dados reais; em particular, starelidreferencia o índice. Entretanto, nenhuma entrada é feita para uma coluna de índice de não expressão comum, pois seria redundante com a entrada da coluna da tabela subjacente.

No entanto, a criação do índice não faz com que o Postgres colete estatísticas. Tentar:

CREATE INDEX myindex ON mytable ((myid/100000));
SELECT * FROM pg_statistic WHERE starelid = 'myindex'::regclass;

Não retorna nada até você executar seu primeiro ANALYZE(ou VACUUM ANALYZE, ou o daemon de autovacuum entra em ação).

ANALYZE mytable;
SELECT * FROM pg_statistic WHERE starelid = 'myindex'::regclass;

Agora você verá estatísticas adicionadas.

Como a tabela inteira precisa ser lida de qualquer maneira, o Postgres usará uma varredura seqüencial, a menos que espere que o cálculo myid/100000seja caro o suficiente para alternar, o que não é.

Sua única outra chance seria uma verificação somente de índice se o índice for muito menor que a tabela - e as condições prévias para uma verificação somente de índice forem atendidas. Detalhes no Wiki do Postgres e no manual .

Enquanto esse índice funcional não for usado, o benefício colateral das estatísticas adicionadas é moderado. Se a tabela fosse somente leitura, o custo seria baixo - mas, novamente, provavelmente veríamos uma verificação somente de índice imediatamente.

Talvez você também possa obter melhores planos de consulta definindo um destino de estatísticas mais alto para mytable.myid. Isso custaria apenas um custo menor. Mais:

Erwin Brandstetter
fonte
Obrigado por esta explicação, é muito útil para entender o problema. No meu caso, provavelmente vou precisar de uma myid/100000 BETWEEN somevalue AND othervaluecondição adicional , portanto o índice será usado no plano de consulta de qualquer maneira - acabei de fazer essa pergunta porque não entendi por que o índice é útil no caso de toda a tabela.
liori 29/07
@ liori: você pode cobrir isso com WHERE myid BETWEEN somevalue*100000 AND othervalue*100000(considere os efeitos de arredondamento, dependendo do seu tipo), e provavelmente já possui um índice simples myid, para que possa ficar sem um índice especializado adicional. Pode ser mais eficiente.
Erwin Brandstetter
6

Quando você cria um índice de expressão, ele faz com que o PostgreSQL colete estatísticas sobre essa expressão. Com essas estatísticas em mãos, agora ela tem uma estimativa precisa do número de linhas agregadas que a consulta retornará, o que a leva a fazer uma melhor escolha de plano.

Especificamente nesse caso, sem essas estatísticas extras, a tabela de hash seria grande demais para caber no work_mem, portanto, não foi escolhido esse método.

jjanes
fonte
Eu acho que o planejador não leva work_memem consideração o valor . Se você o criou para que o tipo caiba na memória, ainda assim usaria o mesmo plano. Deixe-me observar aqui que a diferença de horário (a maior parte) vem da classificação do disco externo.
Dezso
11
@dezso E se você experimentalmente duplicar ou triplicar o valor de work_mem necessário para ajustar o tipo na memória? A classificação e o hash têm estimativas gerais diferentes e as próprias estimativas não são muito precisas. Além disso, qual versão secundária do 9.3 você está usando?
21134 jjanes