Як правильно налаштувати індекси для запитів на відстань PostGIS?


18

Я будую додаток, який повинен запитувати і повертати кожного Recordв таблиці, яка знаходиться в Xкілометрах від PointX. Recordsі PointXпозиції «s визначаються з (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 якийсь тип індексу, здатний зробити вищезазначений запит виконавцем? Якщо ні, то як би рекомендувався підхід робити те, що мені потрібно?


Переконайтеся, що ви створили правильний індекс, на основі географії, і застосуйте 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.
andrerpena

1
Чи є якась причина, чому не зберігати геокомплекс як географію? І ST_Distance, і ST_DWithin очікують географії. І якби ми це зробили, нам не знадобилася б додаткова геометрична індексація для географії.
andrerpena

Це вже інше питання, і якщо його задати, воно може бути закритим як на основі думки.
Вінс

1
Цей результат отримав у Google і подякував @Vince за вашу відповідь. Найменша різниця примусового введення точки геометрії в геограпію зайняла час мого запиту в середньому від 43 секунд до 10 мсек ..
Сердитий 84

чудовий пост, але я думаю, що `(acos (1.0 - 2 * random ()) * 180.0) / pi ())` невірний. діапазон не від -90 до 90
hxd1011
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.