Come impostare correttamente gli indici per le query di distanza PostGIS?


17

Sto costruendo un'applicazione che dovrebbe interrogare e restituire tutti Recordin una tabella a Xchilometri di distanza PointX. Recordse PointXle posizioni sono determinate dalle (long/lat)informazioni fornite dall'API di Google Geocode.

Sono nuovo di PostGIS. Dopo una rapida ricerca, ho trovato questa domanda . La risposta sembra essere sulla falsariga di:

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

Il problema è: anche se sto iniziando a utilizzare GIS, quando guardo la query sopra, non riesco a immaginare come questo possa usare un indice. Vi sono 2 chiamate di funzione. Immagino che il tavolo venga scansionato per tutti Record. Voglio sbagliarmi :)

Domanda: PostGIS ha qualche tipo di indice in grado di rendere performante la query sopra? In caso contrario, quale sarebbe l'approccio raccomandato facendo ciò di cui ho bisogno?


Assicurati di creare l'indice giusto, su un cast per la geografia, e di applicare un a ST_SetSRID()per il ST_MakePointcast precedente alla geografia nella query.
Vince il

Risposte:


37

Esistono due chiavi per ottenere buone prestazioni di query geodetiche con tabelle di grandi dimensioni con geometrycolonne utilizzando i dati geografici WGS 1984 (SRID 4326):

  1. Usa la ST_DWithinfunzione, che cerca usando un indice spaziale disponibile, e troverà le caratteristiche geografiche con una distanza cartesiana
  2. Costruisci un indice extra sul cast geografico, quindi ST_DWithinpuoi usarlo

Quindi diamo un'occhiata a cosa succede nel mondo reale. Per prima cosa dobbiamo creare e popolare una tabella di un milione di punti casuali:

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 eseguiamo la query ST_Distance, otteniamo la scansione della tabella completa prevista:

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

Ora, se usiamo ST_DWithin, otteniamo ancora una scansione completa della tabella (anche se più veloce):

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 questo è l'ultimo pezzo - Costruire l'indice di copertura (geografia del cast):

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

Infine, l'ottimizzatore sta usando l'indice spaziale e mostra, ma quali sono i tre ordini di grandezza tra amici?

Alcuni avvertimenti:

  • Sono un secchione di database, quindi il mio PC di casa ha 16 GB di RAM, sei core da 3,3 Ghz e un SSD da 256 Gb per il tablespace predefinito del database; il tuo chilometraggio può variare

  • Ho rieseguito la creazione SQL prima di ogni query, per livellare il campo di gioco rispetto alle pagine "calde" nella cache, ma questo potrebbe produrre risultati leggermente diversi perché lo stesso seed casuale non è stato utilizzato per diverse esecuzioni

E una nota:

  • Ho modificato l'intervallo di latitudine originale {-90, + 90} per usare l'arco-coseno per una distribuzione dell'area uguale (meno distorta verso i poli)

1
Questa è una delle migliori risposte che io abbia mai ricevuto nella comunità di Stackexchange. Non l'ho ancora provato, ma hai fornito un esempio completo che potrei capire completamente. Grazie mille @Vince.
andrerpena,

1
C'è qualche motivo per non conservare la geomcol come geografia? Sia ST_Distance che ST_DWithin si aspettano aree geografiche. E se lo facessimo, non avremmo bisogno dell'indice extra che getta la geometria sulla geografia.
andrerpena,

Questa è una domanda diversa e, se posta, potrebbe essere chiusa come opinione.
Vince l'

1
Mi sono imbattuto in questo risultato in Google e grazie @Vince per la tua risposta. La più piccola differenza tra il lancio forzato di un punto geom in una geografia ha portato il mio tempo di query da 43 secondi in media a 10 msec invece.
Angry 84

ottimo post, ma penso che `(acos (1.0 - 2 * random ()) * 180.0) / pi ())` non sia corretto. l'intervallo non va da -90 a 90
hxd1011
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.