Jak poprawnie skonfigurować indeksy dla zapytań o odległość PostGIS?

18

Tworzę aplikację, która ma wysyłać zapytania i zwracać każdy Recordw tabeli Xoddalonej o kilometry PointX. Recordsi PointXpozycje są określane na podstawie (long/lat)informacji dostarczonych przez Google Geocode API.

Jestem nowy w PostGIS. Po szybkich badaniach znalazłem to pytanie . Wydaje się, że odpowiedź jest następująca:

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

Problem polega na tym: chociaż zaczynam dopiero w GIS, kiedy patrzę na powyższe zapytanie, nie mogę sobie wyobrazić, w jaki sposób można użyć indeksu. Istnieją 2 wywołania funkcji. Wyobrażam sobie, że stół jest skanowany dla każdego Record. Chcę się mylić :)

Pytanie: Czy PostGIS ma jakiś typ indeksu zdolny do wykonania powyższego zapytania? Jeśli nie, jakie byłoby zalecane podejście do tego, czego potrzebuję?

andrerpena
źródło
Upewnij się, że zbudowałeś odpowiedni indeks na rzutowaniu na geografię i zastosowałeś ST_SetSRID()do ST_MakePointzapytania przed rzutowaniem na geografię.
Vince,

Odpowiedzi:

38

Istnieją dwa klucze do uzyskania dobrej wydajności zapytań geodezyjnych w przypadku dużych tabel z geometrykolumnami korzystającymi z danych geograficznych WGS 1984 (SRID 4326):

  1. Użyj ST_DWithinfunkcji, która wyszukuje za pomocą dostępnego indeksu przestrzennego i znajdzie obiekty geograficzne z odległością kartezjańską
  2. Zbuduj dodatkowy indeks na obsadzie geograficznej, więc ST_DWithinmożesz go użyć

Spójrzmy więc na to, co dzieje się w prawdziwym świecie. Najpierw musimy utworzyć i wypełnić tabelę z milionem losowych punktów:

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)

Jeśli wykonamy zapytanie ST_Distance, otrzymamy oczekiwany pełny skan tabeli:

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

Teraz, jeśli użyjemy ST_DWithin, nadal otrzymujemy pełny skan tabeli (choć szybszy):

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

I to jest ostatni kawałek - Budowanie indeksu obejmującego (geografia obsady):

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

Wreszcie optymalizator korzysta z indeksu przestrzennego i pokazuje, ale jakie są trzy rzędy wielkości między przyjaciółmi?

Niektóre zastrzeżenia:

  • Jestem kujonem bazy danych, więc mój domowy komputer ma 16 GB pamięci RAM, sześć rdzeni 3,3 GHz i dysk SSD 256 GB dla domyślnego obszaru tabel bazy danych; twój przebieg może się różnić

  • Ponownie uruchomiłem kreację SQL przed każdym zapytaniem, aby wyrównać pole gry w odniesieniu do „gorących” stron w pamięci podręcznej, ale może to dać nieco inne wyniki, ponieważ ten sam losowy materiał źródłowy nie był używany dla różnych przebiegów

I uwaga:

  • Poprawiłem oryginalny zakres szerokości {-90, + 90}, aby użyć arc-cosinus dla rozkładu o równej powierzchni (mniej odchylony w kierunku biegunów)
Vince
źródło
1
To jedna z najlepszych odpowiedzi, jakie kiedykolwiek otrzymałem w społeczności Stackexchange. Nadal tego nie próbowałem, ale podałeś pełny przykład, który mogłem całkowicie zrozumieć. Dziękuję bardzo @Vince.
andrerpena
1
Czy jest jakiś powód, dla którego nie przechowywać geomcol jako geografii? Zarówno ST_Distance, jak i ST_DWithin oczekują geografii. Gdybyśmy to zrobili, nie potrzebowalibyśmy dodatkowej geometrii odlewania indeksów do geografii.
andrerpena
To jest inne pytanie i jeśli zostanie zadane, może zostać zamknięte jako oparte na opiniach.
Vince
1
Natknąłem się na ten wynik w Google i dziękuję @Vince za odpowiedź. Najmniejsza różnica polegająca na silnym rzuceniu punktu geomicznego na geograhpy zajęła mi czas zapytania ze średnio 43 sekund do 10 ms zamiast tego
Angry 84
świetny post, ale myślę, że `(acos (1.0 - 2 * random ()) * 180.0) / pi ())` jest niepoprawny. zakres nie jest od -90 do 90
hxd1011