Varredura Sequencial do PostgreSQL em vez de Varredura de Índice Por que?

11

Oi pessoal Eu tenho um problema com minha consulta ao banco de dados PostgreSQL e gostaria de saber se alguém pode ajudar. Em alguns cenários, minha consulta parece ignorar o índice que eu criei que é usado para ingressar nas duas tabelas datae data_area. Quando isso acontece, ele usa uma varredura seqüencial e resulta em uma consulta muito mais lenta.

Varredura sequencial (~ 5 minutos)

Unique  (cost=15368261.82..15369053.96 rows=200 width=1942) (actual time=301266.832..301346.936 rows=153812 loops=1)
   CTE data
     ->  Bitmap Heap Scan on data  (cost=6086.77..610089.54 rows=321976 width=297) (actual time=26.286..197.625 rows=335130 loops=1)
           Recheck Cond: (datasetid = 1)
           Filter: ((readingdatetime >= '1920-01-01 00:00:00'::timestamp without time zone) AND (readingdatetime <= '2013-03-11 00:00:00'::timestamp without time zone) AND (depth >= 0::double precision) AND (depth <= 99999::double precision))
           ->  Bitmap Index Scan on data_datasetid_index  (cost=0.00..6006.27 rows=324789 width=0) (actual time=25.462..25.462 rows=335130 loops=1)
                 Index Cond: (datasetid = 1)
   ->  Sort  (cost=15368261.82..15368657.89 rows=158427 width=1942) (actual time=301266.829..301287.110 rows=155194 loops=1)
         Sort Key: data.id
         Sort Method: quicksort  Memory: 81999kB
         ->  Hash Left Join  (cost=15174943.29..15354578.91 rows=158427 width=1942) (actual time=300068.588..301052.832 rows=155194 loops=1)
               Hash Cond: (data_area.area_id = area.id)
               ->  Hash Join  (cost=15174792.93..15351854.12 rows=158427 width=684) (actual time=300066.288..300971.644 rows=155194 loops=1)
                     Hash Cond: (data.id = data_area.data_id)
                     ->  CTE Scan on data  (cost=0.00..6439.52 rows=321976 width=676) (actual time=26.290..313.842 rows=335130 loops=1)
                     ->  Hash  (cost=14857017.62..14857017.62 rows=25422025 width=8) (actual time=300028.260..300028.260 rows=26709939 loops=1)
                           Buckets: 4194304  Batches: 1  Memory Usage: 1043357kB
                           ->  Seq Scan on data_area  (cost=0.00..14857017.62 rows=25422025 width=8) (actual time=182921.056..291687.996 rows=26709939 loops=1)
                                 Filter: (area_id = ANY ('{28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11}'::integer[]))
               ->  Hash  (cost=108.49..108.49 rows=3349 width=1258) (actual time=2.256..2.256 rows=3349 loops=1)
                     Buckets: 1024  Batches: 1  Memory Usage: 584kB
                     ->  Seq Scan on area  (cost=0.00..108.49 rows=3349 width=1258) (actual time=0.007..0.666 rows=3349 loops=1)
 Total runtime: 301493.379 ms

Varredura de índice (~ 3 segundos) ( em explan.depesz.com )

Unique  (cost=17352256.47..17353067.50 rows=200 width=1942) (actual time=3603.303..3681.619 rows=153812 loops=1)
   CTE data
     ->  Bitmap Heap Scan on data  (cost=6284.60..619979.56 rows=332340 width=297) (actual time=26.201..262.314 rows=335130 loops=1)
           Recheck Cond: (datasetid = 1)
           Filter: ((readingdatetime >= '1920-01-01 00:00:00'::timestamp without time zone) AND (readingdatetime <= '2013-03-11 00:00:00'::timestamp without time zone) AND (depth >= 0::double precision) AND (depth <= 99999::double precision))
           ->  Bitmap Index Scan on data_datasetid_index  (cost=0.00..6201.51 rows=335354 width=0) (actual time=25.381..25.381 rows=335130 loops=1)
                 Index Cond: (datasetid = 1)
   ->  Sort  (cost=17352256.47..17352661.98 rows=162206 width=1942) (actual time=3603.302..3623.113 rows=155194 loops=1)
         Sort Key: data.id
         Sort Method: quicksort  Memory: 81999kB
         ->  Hash Left Join  (cost=1296.08..17338219.59 rows=162206 width=1942) (actual time=29.980..3375.921 rows=155194 loops=1)
               Hash Cond: (data_area.area_id = area.id)
               ->  Nested Loop  (cost=0.00..17334287.66 rows=162206 width=684) (actual time=26.903..3268.674 rows=155194 loops=1)
                     ->  CTE Scan on data  (cost=0.00..6646.80 rows=332340 width=676) (actual time=26.205..421.858 rows=335130 loops=1)
                     ->  Index Scan using data_area_pkey on data_area  (cost=0.00..52.13 rows=1 width=8) (actual time=0.006..0.008 rows=0 loops=335130)
                           Index Cond: (data_id = data.id)
                           Filter: (area_id = ANY ('{28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11}'::integer[]))
               ->  Hash  (cost=1254.22..1254.22 rows=3349 width=1258) (actual time=3.057..3.057 rows=3349 loops=1)
                     Buckets: 1024  Batches: 1  Memory Usage: 584kB
                     ->  Index Scan using area_primary_key on area  (cost=0.00..1254.22 rows=3349 width=1258) (actual time=0.012..1.429 rows=3349 loops=1)
 Total runtime: 3706.630 ms

Estrutura da tabela

Esta é a estrutura da data_areatabela para a tabela. Posso fornecer as outras tabelas, se necessário.

CREATE TABLE data_area
(
  data_id integer NOT NULL,
  area_id integer NOT NULL,
  CONSTRAINT data_area_pkey PRIMARY KEY (data_id , area_id ),
  CONSTRAINT data_area_area_id_fk FOREIGN KEY (area_id)
      REFERENCES area (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT data_area_data_id_fk FOREIGN KEY (data_id)
      REFERENCES data (id) MATCH SIMPLE
      ON UPDATE CASCADE ON DELETE CASCADE
);

INQUERIR

WITH data AS (
    SELECT * 
    FROM data 
    WHERE 
        datasetid IN (1) 
        AND (readingdatetime BETWEEN '1920-01-01' AND '2013-03-11') 
        AND depth BETWEEN 0 AND 99999
)
SELECT * 
FROM ( 
    SELECT DISTINCT ON (data.id) data.id, * 
    FROM 
        data, 
        data_area 
        LEFT JOIN area ON area_id = area.id 
    WHERE 
        data_id = data.id 
        AND area_id IN (28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11) 
) as s;

Retorna 153812linhas. Desativou set enable_seqscan= false;a verificação seqüencial e obteve o resultado do índice.

Eu tentei fazer um ANALYSEno banco de dados e aumentar as estatísticas reunidas nas colunas usadas na consulta, mas nada parece ajudar.

Alguém poderia espalhar e esclarecer isso ou sugerir mais alguma coisa que eu deveria tentar?

Mark Davidson
fonte
Ajudaria -me se você incluiu as consultas que geraram cada um desses planos de execução.
Mike Sherrill 'Cat Recall'
Uma diferença de 2 ordens de magnitude no número estimado de linhas e no número real de linhas? Estou lendo isso certo?
Mike Sherrill 'Cat Recall'
@ Catcall Adicionou a consulta (meio fundamental para poder descobrir o que está acontecendo). Quando você se refere às linhas estimadas, são 200 e, na verdade, 153812?
Mark Davidson
2
Sim, 200 vs 150k parece estranho à primeira vista. Existe um motivo convincente para misturar uma junção esquerda com um produto cartesiano ( FROM data, data_area)? À primeira vista, usar DISTINCT ON sem uma cláusula ORDER BY parece ser uma má idéia.
Mike Sherrill 'Cat Recall'
Explique.depesz.com/s/Uzin pode ser informativo.
Craig Ringer

Respostas:

7

Observe esta linha:

->  Index Scan using data_area_pkey on data_area  (cost=0.00..52.13 rows=1 width=8) 
    (actual time=0.006..0.008 rows=0 loops=335130)

Se você calcular o custo total, considerando os loops, é isso 52.3 * 335130 = 17527299. Isso é maior que 14857017.62 para a seq_scanalternativa. É por isso que ele não usa o índice.

Portanto, o otimizador está superestimando o custo da verificação do índice. Eu acho que seus dados estão classificados no índice (devido a um índice em cluster ou como ele foi carregado) e / ou você tem bastante memória cache e / ou um bom disco rápido. Portanto, há pouca E / S aleatória acontecendo.

Você também deve verificar a correlationno pg_stats, que é utilizado pelo otimizador para avaliar agrupamento ao calcular o custo índice, e, finalmente, tentar mudar random_page_coste cpu_index_tuple_cost, para corresponder ao seu sistema.

piada
fonte
A menos que eu estou faltando alguma coisa, eu acho que @jop significava 52.13, não 52.3, o que resultaria em 17470326,9 (ainda maior do que o seq_scan)
BotNet
2

Seu CTE, na verdade, nada mais faz do que terceirizar algumas WHEREcondições, a maioria delas equivalente WHERE TRUE. Como as CTEs geralmente estão atrás de uma cerca de otimização (o que significa que ela é otimizada por si só), elas podem ajudar muito em determinadas consultas. Nesse caso, no entanto, eu esperaria o efeito exatamente oposto.

O que eu tentaria é reescrever a consulta para ser o mais simples possível:

SELECT d.id, * 
FROM 
    data d 
    JOIN data_area da ON da.data_id = d.id
    LEFT JOIN area a ON da.area_id = a.id 
WHERE 
    d.datasetid IN (1) 
    AND da.area_id IN (28,29,30,31,32,33,25,26,27,18,19,20,21,12,13,14,15,16,17,34,35,1,2,3,4,5,6,22,23,24,7,8,9,10,11) 
    AND (readingdatetime BETWEEN '1920-01-01' AND '2013-03-11') -- this and the next condition don't do anything, I think
    AND depth BETWEEN 0 AND 99999
;

e verifique se o índice é usado ou não. Ainda é muito possível que você não precise de todas as colunas de saída (pelo menos as duas colunas da tabela de junção são supérfluas).

Por favor, reporte e diga qual versão do PostgreSQL você usa.

dezso
fonte
Obrigado pela sua sugestão, minhas desculpas pela minha resposta atrasada à sua postagem. Estive trabalhando em outros projetos. Sua sugestão realmente significa que a consulta agora parece usar o índice de maneira confiável para todas as consultas, mas ainda não estou obtendo o desempenho esperado. Eu fiz uma análise em uma consulta que tem muito mais dados. O explic.depesz.com/s/1yu leva 4 minutos, com 95% do tempo gasto na verificação INDEX.
Mark Davidson
Esqueceu-se de mencionar que estou usando a versão 9.1.4
Mark Davidson
Basicamente, a verificação do índice é bastante rápida, o problema é que é repetida alguns milhões de vezes. O que você ganha se SET enable_nestloop=offantes de executar a consulta?
Dezso
-1

Para os seguidores, eu tive um problema semelhante que era como

select * from table where bigint_column between x and y and mod(bigint_column, 10000) == z

O problema era que minha bigint_column "entre x e y" tinha um índice, mas minha consulta era basicamente "todas as linhas" nessa tabela, por isso não estava usando o índice [já que ele precisava varrer toda a tabela], mas estava fazendo uma varredura seqüencial seq_scan. Uma correção para mim foi criar um novo índice para o lado "mod" da equação, para que ele pudesse ser usado em uma expressão .

rogerdpack
fonte
downvoters sinta-se livre para deixar comentários sobre o porquê :)
rogerdpack