Как я могу создать индекс по широте / долготе, используя GIST в сочетании с логическим цветом? - PullRequest
1 голос
/ 21 января 2020

У меня есть такая таблица:

CREATE TABLE products (
  id INT,
  latitude numeric(9,6),
  longitude numeric(9,6),
  is_location_independent boolean
);

INSERT INTO products (id, latitude, longitude, is_location_independent) VALUES (1, 56.1508469,10.2128301,false);
INSERT INTO products (id, latitude, longitude, is_location_independent) VALUES (2,56.1508469,15.2128301,true);

Я хочу создать индекс, который эффективно охватывает следующий запрос:

SELECT COUNT(*) FROM products
  WHERE 
    (ST_DWithin( ST_GeographyFromText( 'SRID=4326;POINT(' || longitude || ' ' || latitude || ')' ), ST_GeographyFromText('SRID=4326;POINT(13.621700 45.940900)'), 80000 ) OR is_location_independent IS TRUE)

Я пытался создать:

CREATE INDEX CONCURRENTLY index_products_location
    ON products USING gist
    (st_geographyfromtext(((('SRID=4326;POINT('::text || longitude) || ' '::text) || latitude) || ')'::text), is_location_independent)
    TABLESPACE pg_default;

Но я получаю следующее исключение:

ERROR: data type boolean has no default operator class for access method "gist"

Теперь индекс без комбинации с is_location_independent не будет работать хорошо. Без этого планировщик запросов решает выполнить последовательное сканирование таблицы, и в ней миллионы строк, поэтому он работает медленно (8 se c).

Что я могу сделать?

PS: Postgresql 11

Обновление

После добавления следующих двух индексов:

CREATE INDEX ON products USING gist (
   st_geographyfromtext(
      'SRID=4326;POINT(' || longitude || ' ' || latitude || ')'
   )
) WHERE is_location_independent;

и

CREATE INDEX ON products USING btree (
   is_location_independent
);

И имея такие данные, как: is_location_independent: true => 1493 записей и is_location_independent: false => 1 359 200

При выполнении запроса планировщик запросов фактически не использует индекс GEO, то есть тот, который было бы очень полезно для запроса ...

"Finalize Aggregate  (cost=262104.14..262104.15 rows=1 width=8) (actual time=2297.030..2297.030 rows=1 loops=1)"
"  ->  Gather  (cost=262103.72..262104.13 rows=4 width=8) (actual time=2292.063..2667.239 rows=5 loops=1)"
"        Workers Planned: 4"
"        Workers Launched: 4"
"        ->  Partial Aggregate  (cost=261103.72..261103.73 rows=1 width=8) (actual time=2273.565..2273.566 rows=1 loops=5)"
"              ->  Parallel Index Scan using index_products_on_is_location_independent on products  (cost=0.43..261093.34 rows=4152 width=0) (actual time=12.878..2272.703 rows=4461 loops=5)"
"                    Filter: (((st_geographyfromtext((((('SRID=4326;POINT('::text || (longitude)::text) || ' '::text) || (latitude)::text) || ')'::text)) && '0101000020E6100000AED85F764F3E2B40386744696FF84640'::geography) AND ('0101000020E6100000AED85F764F3E2B40386744696FF84640'::geography && _st_expand(st_geographyfromtext((((('SRID=4326;POINT('::text || (longitude)::text) || ' '::text) || (latitude)::text) || ')'::text)), '80000'::double precision)) AND _st_dwithin(st_geographyfromtext((((('SRID=4326;POINT('::text || (longitude)::text) || ' '::text) || (latitude)::text) || ')'::text)), '0101000020E6100000AED85F764F3E2B40386744696FF84640'::geography, '80000'::double precision, true)) OR (is_location_independent IS TRUE))"
"                    Rows Removed by Filter: 257706"

Так что, как вы можете видеть, индексация не работает эффективно ...

Ответы [ 2 ]

2 голосов
/ 21 января 2020

Нет смысла включать is_location_independent в индекс, поскольку вы получили OR в условии WHERE.

Есть две возможности:

  • Если для многих строк is_location_independent установлено значение TRUE, PostgreSQL всегда будет использовать последовательное сканирование, поскольку оно выполняется быстрее.

  • Если только в нескольких строках is_location_independent установлено значение TRUE, создайте второй индекс: обычный индекс B-дерева только для is_location_independent.

    Затем вы можете получить сканирование растрового индекса и растровое изображение или для ускорения запроса.

1 голос
/ 22 января 2020
CREATE INDEX ON products USING gist (
   st_geographyfromtext(
      'SRID=4326;POINT(' || longitude || ' ' || latitude || ')'
   )
) WHERE is_location_independent;

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

Если я создаю индекс с отрицательным WHERE, то с небольшим изменением вашего запроса я могу заставить его использовать оба индекса с a BitmapOr.

explain SELECT COUNT(*) FROM products WHERE 
(
    ST_DWithin( ST_GeographyFromText( 'SRID=4326;POINT(' || longitude || ' ' || latitude || ')' ), ST_GeographyFromText('SRID=4326;POINT(13.621700 45.940900)'), 800000 )
    AND 
    not is_location_independent
) 
OR is_location_independent;

Дает план

Aggregate  (cost=63.09..63.10 rows=1 width=8)
   ->  Bitmap Heap Scan on products  (cost=8.53..63.09 rows=2 width=0)
         Recheck Cond: ((st_dwithin(st_geographyfromtext((((('SRID=4326;POINT('::text || (longitude)::text) || ' '::text) || (latitude)::text) || ')'::text)), '0101000020E6100000AED85F764F3E2B40386744696FF84640'::geography, '800000'::double precision, true) AND (NOT is_location_independent)) OR is_location_independent)
         Filter: ((st_dwithin(st_geographyfromtext((((('SRID=4326;POINT('::text || (longitude)::text) || ' '::text) || (latitude)::text) || ')'::text)), '0101000020E6100000AED85F764F3E2B40386744696FF84640'::geography, '800000'::double precision, true) AND (NOT is_location_independent)) OR is_location_independent)
         ->  BitmapOr  (cost=8.53..8.53 rows=2 width=0)
               ->  Bitmap Index Scan on products_st_geographyfromtext_idx1  (cost=0.00..4.38 rows=1 width=0)
                     Index Cond: (st_geographyfromtext((((('SRID=4326;POINT('::text || (longitude)::text) || ' '::text) || (latitude)::text) || ')'::text)) && _st_expand('0101000020E6100000AED85F764F3E2B40386744696FF84640'::geography, '800000'::double precision))
               ->  Bitmap Index Scan on products_is_location_independent_idx  (cost=0.00..4.14 rows=2 width=0)
                     Index Cond: (is_location_independent = true)

Без ручного переписывания запроса с включением явного "not is_location_independent" ANDed для функции географии, планировщик может ' Выясните, что он может использовать индекс.

С другой стороны, вы можете создать индекс без предложения WHERE, и тогда он будет работать независимо от того, переписываете ли вы запрос, как я предлагаю, или нет. Поскольку подавляющее большинство ваших записей зависит от местоположения, при создании частичного индекса не так много эффективности.

Я создал 3 индекса ниже. Первые 2 используются в вашем исходном запросе, 1-й и 3-й используются в моем его переписывании. Я также сделал версию первого индекса, который был отфильтрован только для WHERE is_location_indepedent, и этот индекс будет использоваться вместо оригинала.

"products_is_location_independent_idx" btree (is_location_independent)
"products_st_geographyfromtext_idx" gist (st_geographyfromtext(((('SRID=4326;POINT('::text || longitude) || ' '::text) || latitude) || ')'::text))
"products_st_geographyfromtext_idx1" gist (st_geographyfromtext(((('SRID=4326;POINT('::text || longitude) || ' '::text) || latitude) || ')'::text)) WHERE NOT is_location_independent
...