Нужна помощь в оптимизации лат / лон гео поиска для mysql - PullRequest
4 голосов
/ 04 июня 2009

У меня есть таблица myisam mysql (5.0.22) с примерно 300 тыс. Записей в ней, и я хочу выполнить поиск по широте / долготе в радиусе пяти миль.

У меня есть индекс, который охватывает поля широты и долготы и быстрый (ответ в миллисекундах), когда я просто выбираю широту / долготу. Но когда я выбираю дополнительные поля в таблице, это ужасно замедляется до 5-8 секунд.

Я использую myisam для использования полнотекстового поиска. Другие индексы работают хорошо (например, выберите * из списка, где slug = 'xxxxx').

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

Моя схема:

CREATE TABLE  `Listing` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `name` varchar(125) collate utf8_unicode_ci default NULL,
  `phone` varchar(18) collate utf8_unicode_ci default NULL,
  `fax` varchar(18) collate utf8_unicode_ci default NULL,
  `email` varchar(55) collate utf8_unicode_ci default NULL,
  `photourl` varchar(55) collate utf8_unicode_ci default NULL,
  `thumburl` varchar(5) collate utf8_unicode_ci default NULL,
  `website` varchar(85) collate utf8_unicode_ci default NULL,
  `categoryid` int(10) unsigned default NULL,
  `addressid` int(10) unsigned default NULL,
  `deleted` tinyint(1) default NULL,
  `status` int(10) unsigned default '2',
  `parentid` int(10) unsigned default NULL,
  `organizationid` int(10) unsigned default NULL,
  `listinginfoid` int(10) unsigned default NULL,
  `createuserid` int(10) unsigned default NULL,
  `createdate` datetime default NULL,
  `lasteditdate` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
  `lastedituserid` int(10) unsigned default NULL,
  `slug` varchar(155) collate utf8_unicode_ci default NULL,
  `aclid` int(10) unsigned default NULL,
  `alt_address` varchar(80) collate utf8_unicode_ci default NULL,
  `alt_website` varchar(80) collate utf8_unicode_ci default NULL,
  `lat` decimal(10,7) default NULL,
  `lon` decimal(10,7) default NULL,
  `city` varchar(80) collate utf8_unicode_ci default NULL,
  `state` varchar(10) collate utf8_unicode_ci default NULL,
  PRIMARY KEY  (`id`),
  KEY `idx_fetch` USING BTREE (`slug`,`deleted`),
  KEY `idx_loc` (`state`,`city`),
  KEY `idx_org` (`organizationid`,`status`,`deleted`),
  KEY `idx_geo_latlon` USING BTREE (`status`,`lat`,`lon`),
  FULLTEXT KEY `idx_name` (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=DYNAMIC;

Мой запрос:

SELECT Listing.name, Listing.categoryid, Listing.lat, Listing.lon
, 3956 * 2 * ASIN(SQRT( POWER(SIN((Listing.lat - 37.369195) * pi()/180 / 2), 2) + COS(Listing.lat * pi()/180) * COS(37.369195 * pi()/180) * POWER(SIN((Listing.lon --122.036849) * pi()/180 / 2), 2) )) rawgeosearchdistance
FROM Listing
WHERE
    Listing.status = '2'
    AND ( Listing.lon between -122.10913433498 and -121.96456366502 )
    AND ( Listing.lat between 37.296909665016 and 37.441480334984)
HAVING rawgeosearchdistance < 5
ORDER BY rawgeosearchdistance ASC;

Объяснить план без геопоиска:

    +----+-------------+------------+-------+-----------------+-----------------+---------+------+------+-------------+
    | id | select_type | table      | type  | possible_keys   | key             | key_len |ref | rows | Extra       |
    +----+-------------+------------+-------+-----------------+-----------------+---------+------+------+-------------+
    |  1 | SIMPLE      | Listing    | range | idx_geo_latlon  | idx_geo_latlon  | 19      | NULL |  453 | Using where |
    +----+-------------+------------+-------+-----------------+-----------------+---------+------+------+-------------+

Объясните план с помощью геоисследования:

+----+-------------+------------+-------+-----------------+-----------------+---------+------+------+-----------------------------+
| id | select_type | table      | type  | possible_keys   | key             | key_len | ref  | rows | Extra                       |
+----+-------------+------------+-------+-----------------+-----------------+---------+------+------+-----------------------------+
|  1 | SIMPLE      | Listing    | range | idx_geo_latlon  | idx_geo_latlon  | 19      | NULL |  453 | Using where; Using filesort |
+----+-------------+------------+-------+-----------------+-----------------+---------+------+------+-----------------------------+

Вот план объяснения с индексом покрытия. Наличие столбцов в правильном порядке имело большое значение:

+----+-------------+--------+-------+---------------+---------------+---------+------+--------+------------------------------------------+
| id | select_type | table  | type  | possible_keys | key           | key_len | ref  | rows   | Extra                                    |
+----+-------------+--------+-------+---------------+---------------+---------+------+--------+------------------------------------------+
|  1 | SIMPLE      | Listing | range | idx_geo_cover | idx_geo_cover | 12      | NULL | 453     | Using where; Using index; Using filesort |
+----+-------------+--------+-------+---------------+---------------+---------+------+--------+------------------------------------------+

Спасибо!

Ответы [ 5 ]

4 голосов
/ 04 июня 2009

Я думаю, вы действительно должны рассмотреть возможность использования PostgreSQL (в сочетании с Postgis).

Я отказался от MySQL для геопространственных данных (на данный момент) по следующим причинам:

  • MySQL поддерживает только пространственные типы данных / пространственные индексы в таблицах MyISAM с внутренними недостатками MyISAM (в отношении транзакций, ссылочной целостности ...)
  • MySQL реализует некоторые из OpenGIS спецификации только на основе MBR (минимальный ограничивающий прямоугольник), который довольно бесполезно для самых серьезных геопространственная обработка запросов (см. эта ссылка в руководстве по MySQL ). Скорее всего, вам понадобятся некоторые из этих функций раньше.

PostgreSQL / Postgis с правильными (GIST) пространственными индексами и правильными запросами может быть чрезвычайно быстрым.

Пример : определив перекрывающиеся полигоны между «небольшим» набором полигонов и таблицей с более чем 5 миллионами (!) Очень сложных полигонов, рассчитайте степень совпадения между этими результатами + сортировка. Среднее время выполнения: от 30 до 100 миллисекунд (у этой конкретной машины много оперативной памяти. Не забудьте настроить установку PostgreSQL ... (см. Документацию)).

1 голос
/ 04 июня 2009

Вы, вероятно, используете «индекс покрытия» в своем запросе только по широте / долготе. Покрывающий индекс возникает, когда индекс, используемый запросом, содержит данные, которые вы выбираете. MySQL нужно только посетить индекс, а не строки данных. См. Это для получения дополнительной информации . Это объясняет, почему запрос lat / lon такой быстрый.

Я подозреваю, что вычисления и количество возвращаемых строк замедляют выполнение более длинного запроса. (плюс любая временная таблица, которая должна быть создана для предложения has).

0 голосов
/ 05 июня 2009

Когда я реализовал поиск по географическому радиусу, я просто загрузил все используемые нами Zip-коды в память с их длиной long, а затем использовал мою начальную точку с радиусом, чтобы получить список zip-кодов в радиусе, а затем использовал это для моего запроса базы данных. Конечно, я использовал solr для поиска, потому что пространство поиска находилось в диапазоне 20 миллионов строк, но должны применяться те же принципы. Извиняюсь за поверхностность этого ответа, так как я разговариваю по телефону.

0 голосов
/ 04 июня 2009

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

Listing1Id, Listing2ID, расстояние

В основном, все расстояния предварительно "рассчитаны"

Тогда вы можете сделать что-то вроде:

Выберите list2ID из v_Distance d где расстояние <5 и перечисление1ID = XXX </p>

0 голосов
/ 04 июня 2009

Вы действительно должны избегать таких сложных математических операций в своем утверждении select. Это, вероятно, источник многих ваших замедлений. Помните, что SQL - это язык запросов; он действительно не оптимизирован для тригонометрических функций.

SQL будет быстрее, и ваши общие результаты будут быстрее, если вы будете выполнять очень наивный дистанционный поиск (который даст больше результатов), а затем потерять результаты.

Если вы хотите использовать расстояние в своем запросе, по крайней мере, используйте вычисление квадрата расстояния; Квадратные вычисления печально известны своей медлительностью. Квадратное расстояние намного проще в использовании. Вычисление квадрата расстояния просто использует квадрат расстояния вместо расстояния; это намного проще. Для декартовых систем координат, поскольку сумма квадратов коротких сторон прямоугольного треугольника равна квадрату гипотенузы, проще вычислить квадратное расстояние (просто сложить два квадрата), чем вычислить расстояние; все, что вам нужно сделать, это убедиться, что вы возводите в квадрат расстояние, с которым хотите сравнить (поэтому вместо того, чтобы находить точное расстояние и сравнивать его с желаемым расстоянием (скажем, 5), вы находите квадратное расстояние и сравниваете это. на квадрат желаемого расстояния (25, если желаемое расстояние было 5).

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