Postgres - полное сканирование таблицы слишком медленное - индекс не используется - PullRequest
2 голосов
/ 09 декабря 2011

У меня есть таблица в базе данных postgres со многими столбцами, среди которых у меня есть:

n_store_object_id     integer,
n_latitude            decimal,
n_longitude           decimal

В настоящее время в таблице около 250 000 строк.

Мне нужно найти записи с ненулевым store_object_id, расположенным на фиксированном расстоянии от заданного местоположения. Для вычисления расстояния у меня есть следующая функция:

CREATE OR REPLACE FUNCTION fn_geo_distance(numeric, numeric, numeric, numeric)
  RETURNS numeric AS
$BODY$
declare
    lat1d       ALIAS for $1;
    lon1d       ALIAS for $2;
    lat2d       ALIAS for $3;
    lon2d       ALIAS for $4;

    lat1        DECIMAL := lat1d / 57.29577951;
    lon1        DECIMAL := lon1d / 57.29577951;
    lat2        DECIMAL := lat2d / 57.29577951;
    lon2        DECIMAL := lon2d / 57.29577951;
begin
    return 3963.0 * acos(sin(lat1) * sin(lat2) + cos(lat1) * cos(lat2) * cos(lon2 - lon1));
end;$BODY$
  LANGUAGE plpgsql IMMUTABLE;

Теперь запрос, который мне нужен, прост:

select *
  from objects
 where n_store_object_id is not null
   and fn_geo_distance(51.5, 0, n_latitude, n_longitude) <= 20

Это занимает довольно много времени - и когда я "объясняю" этот запрос, я вижу полное сканирование таблицы. Справедливо. Поэтому я создаю индекс для этих трех столбцов:

create index idx_object_location on objects(n_store_object_id, n_latitude, n_longitude)

Я перезапустил запрос выше - и это все еще занимает много времени. «Объяснение» показывает, что вновь созданный индекс не используется. Я что-то пропустил? Почему он не используется и как я могу заставить двигатель использовать его? О, и в первую очередь, поможет ли этот индекс?

Спасибо!

Ответы [ 5 ]

6 голосов
/ 09 декабря 2011

Ваши индексные ордера по ID, затем по lat, затем по long.Это не поможет, потому что он не может определить диапазон идентификаторов для поиска.

Вы не можете индексировать это хорошо, используя обычные индексы "btree" (по умолчанию в postgres и любом другом sql).Если вы задумаетесь над проблемой на мгновение, большинство индексов основаны на порядке упорядочения (в цифровом или алфавитном порядке).Но вы не можете заказать географию.Вы можете упорядочить вещи в порядке их расстояния от одной точки, но когда вы перемещаете эту точку, некоторые вещи будут ближе, другие будут дальше, поэтому порядок изменится.

Best ... Для этой проблемы созданы специальные индексы.Поскольку вы используете postgres, я предлагаю вам прочитать о GiST.http://postgis.net/docs/manual-2.0/using_postgis_dbmanagement.html (пожалуйста, Google, а также перейдите по этой ссылке).

Теперь он включен в состав postgres и специально разработан для работы с географией.

Альтернативно... Вторичным решением является размещение ДВУХ индексов в данных, одна широта (только), одна логарифмическая (только).И добавьте max и min lat и long к запросу, как упомянуто в другом ответе.Postgres может использовать ОБА индексы вместе, чтобы сузить.Важно использовать два отдельных индекса, а НЕ один, содержащий как lat, так и long.

2 голосов
/ 09 декабря 2011

Индексы не магические.Индексный стиль по умолчанию - это просто b-дерево, которое можно использовать для удовлетворения запросов на indexed_key = value, indexed_key < value и т. Д., Но простое создание в группе столбцов не делает любое выражение, основанное на значениях этих столбцов, сразу эффективным.

Postgresql, начиная с 9.1, не поддерживает использование индекса в качестве «индекса покрытия» для сокращения объема дискового ввода-вывода, необходимого для полного сканирования.9,2 будет.В то же время, если вы считаете, что это будет полезно, используйте триггеры для заполнения вспомогательной таблицы, что, по сути, одно и то же, только без возможности автоматического ее использования из запросов.Но это не меняет того факта, что вы будете выполнять кучу триггерных вычислений для каждой из 250 000 строк.

Если вы действительно хотите выполнить такую ​​геопространственную индексацию, используйте расширения cube / earthdistanceпостроить индекс r-дерева GiST по координатам.Это позволит вам использовать поиск по индексу для запросов в форме «найти все точки в этом поле», а затем вы можете добавить дополнительные критерии функции для обрезки результатов, которые находятся в поле, но находятся за пределами вашей целевой сферы.

1 голос
/ 09 декабря 2011

У меня аналогичная настройка и я использую стандартный тип PostgreSQL point для широты / долготы. Следующее работает с PostgreSQL 8.4 +.

CREATE table object(
 object_id serial PRIMARY KEY
,geocode point
);

Затем я добавляю индекс GIST следующим образом:

CREATE INDEX object_geocode_idx
ON object
USING gist (box(geocode, geocode));

Обратите внимание, как я индексирую виртуальную рамку , образованную двумя точками - теми же двумя точками в случае индекса.
Кроме того, я кластер моей таблицы по этому индексу, поэтому нужно будет извлечь минимум блоков.

ALTER TABLE object CLUSTER ON object_geocode_idx;

Теперь попробуйте поискать так:

SELECT *
FROM   object
WHERE  box(geocode,geocode) <@ box(mypoint1, mypoint2);

Прочтите об операторе «содержится в» в руководстве .
Проверьте с EXPLAIN ANALYZE, если индекс используется. Если это так, запрос должен быстро проясниться. Сделайте эту коробку достаточно большой, чтобы включить все ваши очки. Примените дополнительные критерии, если хотите избавиться от буквальных угловых дел. Это будет дешево.

1 голос
/ 09 декабря 2011

Другое ограничение вашего запроса - это результат функции, единственный способ получить ее - выполнить ее для всех ненулевых значений.

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

то есть, если бы вы могли рассчитать минимальную и максимальную длину и широту, которые стоило потрудиться вычислитьТогда вы могли бы усилить ограничение с

and (n_latitude between LaMin and LaMax) and (n_longitude between loMin and loMax)
0 голосов
/ 09 декабря 2011

Вам нужно будет создать индекс на основе функции:

create index idx_object_distance on objects(fn_geo_distance(51.5, 0, n_latitude, n_longitude))

Обновление

, как предложил Тони Хопкинсон, другой вариант, который вы можете использовать между для фильтрации диапазонов

Вам понадобятся два отдельных индекса, чтобы это произошло быстро:

create index idx_object_latitude on objects(n_latitude);
create index idx_object_longitude on objects(n_longitude);

база данных будет сканировать оба индекса и выполнить объединение при объединениирезультаты

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...