Limitar linhas através da função espacial

9

Estou tentando melhorar o desempenho da consulta abaixo. Independentemente de como eu escrevo a consulta (subconsulta na cláusula FROM, subconsulta na cláusula WHERE), o postgres insiste em executar todas as ~ 570K linhas através da dispendiosa função ST_DWITHIN, embora existam apenas 60 linhas em que county = 24. Como faço para o postgres filtrar no county = 24 ANTES de executar a função postgis, que me parece muito mais rápida e muito mais eficiente? 700ms não é motivo de muita preocupação, mas, como esta tabela aumenta para 10 milhões, estou preocupado com o desempenho.

Observe também que p.id é uma chave primária, p.zipcode é um índice fk, z.county é um índice fk e p.geom possui um índice GiST.

Inquerir:

EXPLAIN ANALYZE
  SELECT count(p.id)
  FROM point AS p
  LEFT JOIN zipcode AS z
    ON p.zipcode = z.zipcode
  WHERE z.county = 24
    AND ST_DWithin(
      p.geom, 
      ST_SetSRID(ST_Point(-121.479756008715,38.563236291512),4269), 
      16090.0,
      false
    )

EXPLIQUE A ANÁLISE:

Aggregate  (cost=250851.91..250851.92 rows=1 width=4) (actual time=724.007..724.007 rows=1 loops=1)
  ->  Hash Join  (cost=152.05..250851.34 rows=228 width=4) (actual time=0.359..723.996 rows=51 loops=1)
        Hash Cond: ((p.zipcode)::text = (z.zipcode)::text)
        ->  Seq Scan on point p  (cost=0.00..250669.12 rows=7437 width=10) (actual time=0.258..723.867 rows=63 loops=1)
              Filter: (((geom)::geography && '0101000020AD10000063DF8B52B45E5EC070FB752018484340'::geography) AND ('0101000020AD10000063DF8B52B45E5EC070FB752018484340'::geography && _st_expand((geom)::geography, 16090::double precision)) AND _st_dwithin((g (...)
              Rows Removed by Filter: 557731
        ->  Hash  (cost=151.38..151.38 rows=54 width=6) (actual time=0.095..0.095 rows=54 loops=1)
              Buckets: 1024  Batches: 1  Memory Usage: 3kB
              ->  Bitmap Heap Scan on zipcode z  (cost=4.70..151.38 rows=54 width=6) (actual time=0.023..0.079 rows=54 loops=1)
                    Recheck Cond: (county = 24)
                    Heap Blocks: exact=39
                    ->  Bitmap Index Scan on fki_zipcode_county_foreign_key  (cost=0.00..4.68 rows=54 width=0) (actual time=0.016..0.016 rows=54 loops=1)
                          Index Cond: (county = 24)
Planning time: 0.504 ms
Execution time: 724.064 ms
Josh
fonte
Talvez tente alterar a linha "aponte como p deixou o código postal como z" para algo como "aponte como p deixou o código postal (SELECT * FROM zipcode WHERE zipcode.county = 24) como z"?
weiji14
Apenas tentei, mesmos resultados. Quando copio as ~ 60 pointlinhas em que county = 24 para uma nova tabela por si só, a consulta leva apenas 0,453 ms em comparação com 724, então há definitivamente uma grande diferença.
Josh
11
Você deve usar count(*)como uma questão de estilo. Se idé um pkid como você diz, é o NOT NULLque significa que eles são iguais. Exceto count(id)tem a desvantagem de que você deve fazer essa pergunta se idfor anulável.
Evan Carroll
11
Posso perguntar por que você está usando uma junção externa esquerda? Tente mudá-lo para uma junção interna ... Os resultados devem ser idênticos
MickyT
Se z.country é o fator limitante, sugiro que você o coloque primeiro em uma consulta CTE e, em seguida, verifique esses resultados para uma interseção com seu ponto de interesse. Como o índice espacial é provavelmente menos seletivo que o county = 24, neste caso, ele está apenas atrapalhando.
John Powell

Respostas:

3

Você pode ver o problema com as contagens de linhas esperadas x reais. O planejador acha que existem 7.437 linhas, no entanto, existem apenas 63. As estatísticas estão desativadas. Curiosamente, também, ele não está usando uma pesquisa de índice (índice) de caixa delimitadora com a qual DWithinvocê pode colar o resultado \d point. Qual versão do PostGIS e PostgreSQL?

Tente correr ANALYZE point. Você obtém o mesmo plano ao mudar a condição?

JOIN zipcode AS z
  ON p.zipcode = z.zipcode
  AND z.county = 24
Evan Carroll
fonte
Eu executei a análise e também tentei a nova condição AND no ON, mas ainda estava recebendo 700ms de tempo de execução. Este é o PGSQL 9.4 e o PostGIS 2.2.
217 Josh
2

Como uma observação lateral, há uma chance razoável de que esse comportamento seja modificado no PostGIS 2.3.0 se você quiser chamá-lo de bug.

Dos documentos no PostgreSQL

Um número positivo que fornece o custo estimado de execução para a função, em unidades de cpu_operator_cost. Se a função retornar um conjunto, esse é o custo por linha retornada. Se o custo não for especificado, será assumida 1 unidade para o idioma C e funções internas e 100 unidades para funções em todos os outros idiomas. Valores maiores fazem com que o planejador evite avaliar a função com mais frequência do que o necessário.

Portanto, o custo padrão foi 1 (muito barato). D_Withinusar um índice GIST é muito barato. Mas, isso foi aumentado para 100 (por proxy do interno _ST_DWithin).

Eu não sou um grande fã do método CTE. CTEs são uma cerca de otimização. Portanto, fazer isso dessa maneira remove algum espaço potencial para otimização futura. Se os padrões mais saudáveis ​​o corrigirem, prefiro atualizar. No final do dia, precisamos fazer o trabalho e esse método claramente funciona para você.

Evan Carroll
fonte
1

Graças à dica de John Powell, revisei a consulta para colocar a condição de limitação do município em uma consulta com / CTE e esse desempenho melhorou um pouco para 222ms vs 700. Ainda está muito longe dos 0,74 ms que recebo quando os dados estão no seu mesa própria. Ainda não sei ao certo por que o planejador não limita o conjunto de dados antes de executar uma função postgis cara, e terei que tentar com conjuntos de dados maiores quando os tiver, mas isso parece ser uma solução para essa situação única no momento.

with points as (
   select p.id, p.geom from point p inner join zipcode z
   on p.zipcode = z.zipcode
   where county = 24
   ) 


SELECT count(points.id)
FROM points
WHERE ST_DWITHIN(points.geom, (ST_SetSRID(ST_Point(-121.479756008715,38.563236291512),4269)), 16090.0, false)
Josh
fonte
11
Teríamos que ver todos os três planos de consulta e o esquema da tabela (solicitado no meu ponto de resposta \ d).
Evan Carroll
0

Você deve criar um índice em zipcode(county, zipcode) , que deve fornecer uma varredura de índice somente em z.

Você também pode querer experimentar com btree_gistextensão criar qualquer point(zipcode, geom)índice ou point(geom, zipcode)e zipcode(zipcode, county)índice.

Jakub Kania
fonte