Как правильно настроить индексы для удаленных запросов PostGIS?

18

Я создаю приложение, которое должно запрашивать и возвращать каждый Recordв таблице, в Xкилометрах от PointX. Recordsи PointXпозиции определяются на основе (long/lat)информации, предоставленной Google Geocode API.

Я новичок в PostGIS. После быстрого исследования я нашел этот вопрос . Ответ, кажется, имеет вид:

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

Проблема в том, что, хотя я только начинаю работать с ГИС, когда я смотрю на приведенный выше запрос, я не могу себе представить, как можно использовать индекс. Есть 2 функциональных вызова. Я представляю таблицу, сканируемую для каждого Record. Хочу ошибаться :)

Вопрос: Есть ли у PostGIS какой-либо индексный тип, способный обеспечить выполнение вышеупомянутого запроса? Если нет, то какой подход рекомендовал бы делать то, что мне нужно?

andrerpena
источник
Убедитесь, что вы создали правильный индекс для приведения к географии и применили ST_SetSRID()к нему ST_MakePointперед приведением к географии в запросе.
Винс

Ответы:

38

Есть два ключа к получению хорошей производительности геодезических запросов с большими таблицами со geometryстолбцами, использующими географические данные WGS 1984 (SRID 4326):

  1. Используйте ST_DWithinфункцию, которая выполняет поиск с использованием доступного пространственного индекса и находит географические объекты с декартовым расстоянием.
  2. Создайте дополнительный указатель на географию, так что ST_DWithinможете использовать его

Итак, давайте посмотрим, что происходит в реальном мире. Сначала нам нужно создать и заполнить таблицу из миллиона случайных точек:

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)

Если мы выполним запрос ST_Distance, мы получим ожидаемое полное сканирование таблицы:

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

Теперь, если мы используем ST_DWithin, мы все равно получаем полное сканирование таблицы (хотя и более быстрое):

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

И это последняя часть - Построение индекса покрытия (география броска):

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

Наконец, оптимизатор использует пространственный индекс, и он показывает, но каковы три порядка величины между друзьями?

Некоторые предостережения:

  • Я ботаник базы данных, поэтому мой домашний ПК имеет 16 ГБ ОЗУ, шесть ядер 3,3 ГГц и SSD 256 ГБ для табличного пространства по умолчанию для базы данных; Ваш пробег может варьироваться

  • Я перезапускал создание SQL перед каждым запросом, чтобы выровнять игровое поле по отношению к «горячим» страницам в кеше, но это могло привести к немного отличающимся результатам, потому что одно и то же случайное начальное число не использовалось для разных запусков

И примечание:

  • Я настроил исходный диапазон широт {-90, + 90}, чтобы использовать арккосинус для распределения по равной площади (с меньшим смещением к полюсам)
Винс
источник
1
Это один из лучших ответов, которые я когда-либо получал в сообществе Stackexchange. Я все еще не пробовал, но вы привели полный пример, который я мог полностью понять. Большое спасибо @Vince.
Андреперпена
1
Есть ли причина, почему бы не хранить геомолку как географию? И ST_Distance, и ST_D с ожидаемой географией. И если бы мы сделали это, нам не понадобилась бы дополнительная геометрия приведения индекса к географии.
andrerpena
Это другой вопрос, и если его спросить, он может быть закрыт как основанный на мнении.
Винс
1
Наткнулся на этот результат в гугле и спасибо @Vince за ответ. Наименьшая разница в принудительном приведении геометрической точки к географии привела к тому, что время моего запроса вместо этого составило от 43 секунд в среднем до 10 мсек.
Сердитый 84
отличный пост, но я думаю `(acos (1.0 - 2 * random ()) * 180.0) / pi ())` не правильно. диапазон не от -90 до 90
hxd1011