Comment configurer correctement les index pour les requêtes à distance PostGIS?


18

Je construis une application qui est censée interroger et renvoyer chaque élément Recorddans une table à des Xkilomètres de distance PointX. Recordset PointXles positions de sont déterminées à partir des (long/lat)informations fournies par l'API Google Geocode.

Je suis nouveau sur PostGIS. Après une recherche rapide, j'ai trouvé cette question . La réponse semble aller dans le sens de:

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

Le problème est: même si je ne fais que commencer au SIG, quand je regarde la requête ci-dessus, je ne peux pas imaginer comment cela peut utiliser un index. Il y a 2 appels de fonction. J'imagine la table en cours d' analyse pour chaque Record. Je veux me tromper :)

Question: PostGIS possède-t-il un type d'index capable de rendre la requête ci-dessus performante? Sinon, quelle serait l'approche recommandée pour faire ce dont j'ai besoin?


Assurez-vous de créer le bon index, sur une conversion vers la géographie, et appliquez un ST_SetSRID()à la ST_MakePointconversion avant la géographie dans la requête.
Vince

Réponses:


38

Il existe deux clés pour obtenir de bonnes performances de requête géodésique avec de grandes tables avec des geometrycolonnes utilisant les données géographiques WGS 1984 (SRID 4326):

  1. Utilisez la ST_DWithinfonction, qui recherche en utilisant un index spatial disponible, et trouvera des entités géographiques avec une distance cartésienne
  2. Créez un index supplémentaire sur la distribution géographique, vous ST_DWithinpouvez donc l' utiliser

Voyons donc ce qui se passe dans le monde réel. Nous devons d'abord créer et remplir un tableau d'un million de points aléatoires:

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)

Si nous exécutons la requête ST_Distance, nous obtenons votre analyse complète de table attendue:

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

Maintenant, si nous utilisons ST_DWithin, nous obtenons toujours une analyse complète de la table (quoique plus rapide):

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

Et ceci est la dernière pièce - Construire l'indice de couverture (géographie moulée):

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

Enfin, l'optimiseur utilise l'index spatial, et cela se voit, mais quels sont les trois ordres de grandeur entre amis?

Quelques mises en garde:

  • Je suis un nerd de base de données, donc mon PC domestique a 16 Go de RAM, six cœurs de 3,3 GHz et un SSD de 256 Go pour l'espace de table par défaut de la base de données; Votre kilométrage peut varier

  • J'ai relancé le SQL de création avant chaque requête, pour niveler le terrain de jeu par rapport aux pages "chaudes" dans le cache, mais cela pourrait produire des résultats légèrement différents car la même graine aléatoire n'a pas été utilisée pour différentes exécutions

Et une note:

  • J'ai modifié la plage de latitude d'origine {-90, + 90} pour utiliser l'arc cosinus pour une distribution de surface égale (moins biaisée vers les pôles)

1
C'est l'une des meilleures réponses que j'ai jamais eues dans la communauté Stackexchange. Je ne l'ai toujours pas essayé, mais vous avez fourni un exemple complet que je pouvais comprendre complètement. Merci beaucoup @Vince.
andrerpena

1
Y a-t-il une raison pour ne pas stocker le géomcol en tant que géographie? ST_Distance et ST_DWinin attendent des zones géographiques. Et si nous le faisions, nous n'aurions pas besoin de la géométrie de coulée d'index supplémentaire pour la géographie.
andrerpena

Il s'agit d'une question différente, et si elle est posée, elle pourrait être classée comme basée sur l'opinion.
Vince

1
Je suis tombé sur ce résultat dans google et merci @Vince pour ta réponse. La plus petite différence de lancer avec force un point geom vers une géograhpie a pris mon temps de requête de 43 secondes en moyenne à 10 ms à la place ..
Angry 84

excellent article, mais je pense que `(acos (1.0 - 2 * random ()) * 180.0) / pi ())` n'est pas correct. la plage n'est pas de -90 à 90
hxd1011
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.