Como configurar corretamente índices para consultas de distância PostGIS?

17

Estou criando um aplicativo que deveria consultar e retornar todos os dados Recordde uma tabela a Xquilômetros de distância PointX. Recordse PointXas posições dos s são determinadas a partir de (long/lat)informações fornecidas pelo Google Geocode API.

Eu sou novo no PostGIS. Após uma pesquisa rápida, encontrei esta pergunta . A resposta parece estar na linha de:

SELECT *
FROM your_table
WHERE ST_Distance_Sphere(the_geom, ST_MakePoint(your_lon,your_lat)) <= radius_mi * 1609.34

O problema é: mesmo que eu esteja apenas começando no GIS, quando olho para a consulta acima, não consigo imaginar como isso pode usar um índice. Existem 2 chamadas de função. Eu imagino a tabela sendo digitalizada para todos Record. Eu quero estar errado :)

Pergunta: O PostGIS tem algum tipo de índice capaz de tornar o desempenho da consulta acima? Caso contrário, qual seria a abordagem recomendada para fazer o que eu preciso?

andrerpena
fonte
Certifique-se de criar o índice correto, em uma conversão para a geografia, e aplique um ST_SetSRID()na ST_MakePointconversão antes da geografia na consulta.
Vince

Respostas:

37

Existem duas chaves para obter um bom desempenho da consulta geodésica com tabelas grandes com geometrycolunas usando dados geográficos WGS 1984 (SRID 4326):

  1. Use a ST_DWithinfunção, que pesquisa usando um índice espacial disponível, e encontrará recursos geográficos com uma distância cartesiana
  2. Crie um índice extra no elenco de geografia, para ST_DWithinpoder usá-lo

Então, vamos ver o que acontece no mundo real. Primeiro, precisamos criar e preencher uma tabela de um milhão de pontos aleatórios:

DROP TABLE IF EXISTS example1
;

CREATE TABLE example1 (
    idcol   serial      NOT NULL,
    geomcol geometry        NULL,
    CONSTRAINT  example1_pk PRIMARY KEY (idcol),
    CONSTRAINT  enforce_srid CHECK (st_srid(geomcol) = 4326)
)
with (
    OIDS=FALSE
);

INSERT INTO example1(geomcol)
SELECT  ST_SetSRID(
            ST_MakePoint(
            (random()*360.0) - 180.0,
            (acos(1.0 - 2.0 * random()) * 2.0 - pi()) * 90.0 / pi()),
            4326) as geomcol
FROM  generate_series(1, 1000000) vtab;

CREATE INDEX example1_spx ON example1 USING GIST (geomcol);
-- (took about 22 sec)

Se executarmos a consulta ST_Distance, obteremos sua verificação de tabela completa esperada:

EXPLAIN ANALYZE VERBOSE
SELECT  count(*)
FROM    example1
WHERE   ST_Distance(geomcol::geography,ST_SetSRID(ST_MakePoint(6.9333,46.8167),4326)::geography) < 30 * 1609.34
;

Aggregate  (cost=274167.33..274167.34 rows=1 width=0) (actual time=4940.531..4940.532 rows=1 loops=1)
  Output: count(*)
  ->  Seq Scan on bob.example1  (cost=0.00..273334.00 rows=333333 width=0) (actual time=592.766..4940.509 rows=11 loops=1)
        Output: idcol, geomcol
        Filter: (_st_distance((example1.geomcol)::geography, '0101000020E61000005D6DC5FEB2BB1B40545227A089684740'::geography, 0::double precision, true) < 48280.2::double precision)
        Rows Removed by Filter: 999989
Planning time: 2.137 ms
Execution time: 4940.568 ms

Agora, se usarmos ST_DWithin, ainda temos uma verificação completa da tabela (embora mais rápida):

EXPLAIN ANALYZE VERBOSE
SELECT  count(*)
FROM    example1
WHERE   ST_DWithin(geomcol::geography,ST_SetSRID(ST_MakePoint(6.9333,46.8167),4326)::geography,30 * 1609.34)
;

Aggregate  (cost=405867.33..405867.34 rows=1 width=0) (actual time=908.716..908.716 rows=1 loops=1)
  Output: count(*)
  ->  Seq Scan on bob.example1  (cost=0.00..405834.00 rows=13333 width=0) (actual time=38.449..908.700 rows=7 loops=1)
        Output: idcol, geomcol
        Filter: (((example1.geomcol)::geography && '0101000020E61000005D6DC5FEB2BB1B40545227A089684740'::geography) AND ('0101000020E61000005D6DC5FEB2BB1B40545227A089684740'::geography && _st_expand((example1.geomcol)::geography, 48280.2::double precision) (...)
        Rows Removed by Filter: 999993
Planning time: 2.017 ms
Execution time: 908.763 ms

E esta é a última peça - Construindo o índice de cobertura (geografia do elenco):

CREATE INDEX example1_gpx ON example1 USING GIST (geography(geomcol));
-- (Takes an extra 13 sec)

EXPLAIN ANALYZE VERBOSE
SELECT  count(*)
FROM    example1
WHERE   ST_DWithin(geomcol::geography,ST_SetSRID(ST_MakePoint(6.9333,46.8167),4326)::geography,30 * 1609.34)
;

Aggregate  (cost=96538.95..96538.96 rows=1 width=0) (actual time=0.775..0.775 rows=1 loops=1)
  Output: count(*)
  ->  Bitmap Heap Scan on bob.example1  (cost=8671.62..96505.62 rows=13333 width=0) (actual time=0.586..0.769 rows=19 loops=1)
        Output: idcol, geomcol
        Recheck Cond: ((example1.geomcol)::geography && '0101000020E61000005D6DC5FEB2BB1B40545227A089684740'::geography)
        Filter: (('0101000020E61000005D6DC5FEB2BB1B40545227A089684740'::geography && _st_expand((example1.geomcol)::geography, 48280.2::double precision)) AND _st_dwithin((example1.geomcol)::geography, '0101000020E61000005D6DC5FEB2BB1B40545227A089684740':: (...)
        Rows Removed by Filter: 14
        Heap Blocks: exact=33
        ->  Bitmap Index Scan on example1_gpx  (cost=0.00..8668.29 rows=200000 width=0) (actual time=0.384..0.384 rows=33 loops=1)
              Index Cond: ((example1.geomcol)::geography && '0101000020E61000005D6DC5FEB2BB1B40545227A089684740'::geography)
Planning time: 2.572 ms
Execution time: 0.820 ms

Finalmente, o otimizador está usando o índice espacial e mostra, mas quais são as três ordens de grandeza entre amigos?

Algumas advertências:

  • Como eu sou um nerd de banco de dados, meu PC doméstico tem 16Gb de RAM, seis núcleos de 3,3Ghz e um SSD de 256Gb para o espaço de tabela padrão do banco de dados; sua milhagem pode variar

  • Executei novamente o SQL de criação antes de cada consulta, para nivelar o campo de jogo em relação às páginas "quentes" no cache, mas isso poderia produzir resultados ligeiramente diferentes porque a mesma semente aleatória não foi usada para execuções diferentes

E uma nota:

  • Ajustei o intervalo original de latitude {-90, + 90} para usar arco-cosseno para uma distribuição de área igual (menos tendenciosa em relação aos pólos)
Vince
fonte
1
Essa é uma das melhores respostas que já recebi na comunidade Stackexchange. Ainda não tentei, mas você forneceu um exemplo completo que eu pude entender completamente. Muito obrigado @Vince.
22617 Andrerpena
1
Existe alguma razão para não armazenar o geomcol como geografia? ST_Distance e ST_DWithin esperam geografias. E se o fizéssemos, não precisaríamos da geometria extra da conversão de índice para a geografia.
andrerpena
Essa é uma pergunta diferente e, se solicitado, pode ser fechada como baseada em opiniões.
Vince
1
Me deparei com esse resultado no google e obrigado @Vince pela resposta. A menor diferença de força lançando um ponto geom a um geograhpy levei o meu tempo de consulta de 43 segundos em média para 10 milissegundos em vez ..
irritado 84
ótimo post, mas acho que `(acos (1.0-2 * aleatório ()) * 180.0) / pi ())` não está correto. o intervalo não é de -90 a 90
hxd1011