Wie richte ich Indizes für PostGIS-Entfernungsabfragen richtig ein?

17

Ich erstelle eine Anwendung, die jeden Recordin einer Tabelle abfragen und zurückgeben soll, von der XKilometer entfernt sind PointX. Recordsund PointX‚s Positionen werden bestimmt aus (long/lat)Informationen , die von Google Geocode API.

Ich bin neu bei PostGIS. Nach einer kurzen Recherche habe ich diese Frage gefunden . Die Antwort scheint zu lauten:

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

Das Problem ist: Auch wenn ich erst mit GIS anfange, kann ich mir bei der obigen Abfrage nicht vorstellen, wie dies einen Index verwenden kann. Es gibt 2 Funktionsaufrufe. Ich stelle mir vor, dass der Tisch nach allen durchsucht wird Record. Ich möchte mich irren :)

Frage: Verfügt PostGIS über einen Indextyp, mit dem die obige Abfrage ausgeführt werden kann? Wenn nicht, was wäre der empfohlene Ansatz, um das zu tun, was ich brauche?

andrerpena
quelle
Stellen Sie sicher , dass Sie den richtigen Index bauen, auf einem guss zu Geographie und wenden ein , ST_SetSRID()um die ST_MakePointzuvor in der Abfrage Geographie Gießen.
Vince

Antworten:

37

Es gibt zwei Schlüssel, um eine gute geodätische Abfrageleistung bei großen Tabellen mit geometrySpalten zu erzielen, die geografische WGS 1984-Daten (SRID 4326) verwenden:

  1. Verwenden Sie die ST_DWithinFunktion, die anhand eines verfügbaren räumlichen Index sucht und Geografie-Features mit einer kartesischen Entfernung findet
  2. Erstellen Sie einen zusätzlichen Index für die geografische Besetzung, damit Sie ST_DWithinihn verwenden können

Schauen wir uns also an, was in der realen Welt passiert. Zuerst müssen wir eine Tabelle mit einer Million zufälliger Punkte erstellen und füllen:

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)

Wenn wir die ST_Distance-Abfrage ausführen, erhalten wir Ihren erwarteten vollständigen Tabellenscan:

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

Wenn wir jetzt verwenden ST_DWithin, erhalten wir immer noch einen vollständigen Tabellenscan (wenn auch einen schnelleren):

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

Und dies ist das letzte Stück - Aufbau des Deckungsindex (Besetzungsgeographie):

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

Schließlich verwendet der Optimierer den räumlichen Index und es wird angezeigt, aber was sind drei Größenordnungen zwischen Freunden?

Einige Einschränkungen:

  • Da ich ein Datenbank-Nerd bin, verfügt mein Heim-PC über 16 GB RAM, sechs 3,3-GHz-Kerne und eine 256-Gbit-SSD für den Standardtabellenbereich der Datenbank. Ihr Kilometerstand kann variieren

  • Ich habe die Erstellungs-SQL vor jeder Abfrage erneut ausgeführt, um das Spielfeld in Bezug auf "heiße" Seiten im Cache auszugleichen. Dies kann jedoch zu geringfügig unterschiedlichen Ergebnissen führen, da derselbe zufällige Startwert nicht für verschiedene Läufe verwendet wurde

Und ein Hinweis:

  • Ich habe den ursprünglichen Breitengradbereich {-90, + 90} optimiert, um den Arcus-Cosinus für eine flächengleiche Verteilung zu verwenden (weniger auf die Pole ausgerichtet).
Vince
quelle
1
Dies ist eine der besten Antworten, die ich jemals in der Stackexchange-Community bekommen habe. Ich habe es immer noch nicht ausprobiert, aber Sie lieferten ein vollständiges Beispiel, das ich vollständig verstehen konnte. Vielen Dank @Vince.
Andrerpena
1
Gibt es einen Grund, das Geomcol nicht als Geografie zu speichern? Sowohl ST_Distance als auch ST_DWithin erwarten geografische Regionen. Und wenn wir das tun würden, bräuchten wir die zusätzliche Geometrie des Indexgusses nicht für die Geographie.
Andrerpena
Dies ist eine andere Frage, die möglicherweise als meinungsbasiert geschlossen wird.
Vince
1
Kam über dieses Ergebnis in Google und danke @Vince für Ihre Antwort. Der kleinste Unterschied, einen Geom-Punkt mit Nachdruck auf eine Geographie zu setzen, hat meine Abfragezeit von durchschnittlich 43 Sekunden auf stattdessen 10 ms erhöht.
Angry 84
toller Beitrag, aber ich denke, `(acos (1.0 - 2 * random ()) * 180.0) / pi ())` ist nicht korrekt. Der Bereich reicht nicht von -90 bis 90
hxd1011