Использует ли mysql мой индекс или нет, и можно ли улучшить производительность geokit? - PullRequest
4 голосов
/ 26 августа 2009

Я использую geokit (acts_as_mappable) в приложении rails, и производительность радиального или ограниченного поиска значительно ухудшается при большом количестве моделей (я пробовал с 1-2 млн, но проблема, без сомнения, возникает раньше этого).

Geokit выполняет все вычисления на основе столбцов lat и lng в таблице (широта и долгота). Для повышения производительности геокит, как правило, добавляет ограничивающее поле «где» с намерением использовать комбинированный индекс широты и долготы для повышения производительности. Однако это все еще невероятно медленно с большим количеством моделей, и мне кажется, что ограничивающий прямоугольник должен помочь намного больше, чем он делает.

Итак, мой вопрос: есть ли способ заставить mysql лучше использовать комбинированный индекс lat / lng или иным образом повысить производительность запросов sql geokit? Или можно сделать более полезным объединенный индекс для широты и долготы?

edit: У меня теперь есть работа с рельсами, и я написал решение более подробно здесь

Больше фона

Например, этот запрос находит все места в пределах 10 миль от заданной точки. (Я добавил .length только для того, чтобы определить, сколько результатов возвращается - есть более хорошие способы сказать это в геоките, но я хотел вызвать более типичный запрос SQL).

Place.find(:all,:origin=>latlng,:within=>10).length

Это займет около 14 секунд на Mac Mini. Вот план объяснения

mysql> explain SELECT *, (ACOS(least(1,COS(0.898529183781244)*COS(-0.0157233221653665)*COS(RADIANS(places.lat))*COS(RADIANS(places.lng))+    ->  COS(0.898529183781244)*SIN(-0.0157233221653665)*COS(RADIANS(places.lat))*SIN(RADIANS(places.lng))+    ->  SIN(0.898529183781244)*SIN(RADIANS(places.lat))))*3963.19)
    ->  AS distance FROM `places` WHERE (((places.lat>51.3373601471464 AND places.lat<51.6264998528536 AND places.lng>-1.13302245886176 AND places.lng<-0.668737541138245)) AND ( (ACOS(least(1,COS(0.898529183781244)*COS(-0.0157233221653665)*COS(RADIANS(places.lat))*COS(RADIANS(places.lng))+
    ->  COS(0.898529183781244)*SIN(-0.0157233221653665)*COS(RADIANS(places.lat))*SIN(RADIANS(places.lng))+
    ->  SIN(0.898529183781244)*SIN(RADIANS(places.lat))))*3963.19)
    ->  <= 10)) 
    -> ;
+----+-------------+--------+-------+-----------------------------+-----------------------------+---------+------+-------+----------+-------------+
| id | select_type | table  | type  | possible_keys               | key                         | key_len | ref  | rows  | filtered | Extra       |
+----+-------------+--------+-------+-----------------------------+-----------------------------+---------+------+-------+----------+-------------+
|  1 | SIMPLE      | places | range | index_places_on_lat_and_lng | index_places_on_lat_and_lng | 10      | NULL | 87554 |   100.00 | Using where | 
+----+-------------+--------+-------+-----------------------------+-----------------------------+---------+------+-------+----------+-------------+

Таким образом, mysql проверяет 87554 строки, хотя количество мест в результате равно 1135 (а количество мест в ограничительной рамке всего 1323).

Это статистика по индексу (которая создается с помощью миграции рельсов add_index: place, [: lat,: lng] ):

| Table  | Non_unique | Key_name                         | Seq_in_index | Column_name      | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
| places |          1 | index_places_on_lat_and_lng      |            2 | lng              | A         |     1373712 |     NULL | NULL   | YES  | BTREE      |         |

И, похоже, это не связано с вычислениями триггера, поскольку выполнение аналогичного запроса для ограничивающего прямоугольника приводит к гораздо более простому запросу, но аналогично плохо работает:

Place.find(:all,:bounds=>GeoKit::Bounds.from_point_and_radius(latlng,10)).length

Предоставляет аналогичный план объяснения:

   mysql> explain SELECT * FROM `places` WHERE ((places.lat>51.3373601471464 AND places.lat<51.6264998528536 AND places.lng>-1.13302245886176 AND places.lng<-0.668737541138245)) ;
    +----+-------------+--------+-------+-----------------------------+-----------------------------+---------+------+-------+----------+-------------+
    | id | select_type | table  | type  | possible_keys               | key                         | key_len | ref  | rows  | filtered | Extra       |
    +----+-------------+--------+-------+-----------------------------+-----------------------------+---------+------+-------+----------+-------------+
    |  1 | SIMPLE      | places | range | index_places_on_lat_and_lng | index_places_on_lat_and_lng | 10      | NULL | 87554 |   100.00 | Using where | 
    +----+-------------+--------+-------+-----------------------------+-----------------------------+---------+------+-------+----------+-------------+

1 Ответ

3 голосов
/ 26 августа 2009

Обычные B-Tree индексы не слишком хороши для подобных запросов.

Для вашего запроса метод доступа range используется при следующем условии:

places.lat > 51.3373601471464 AND places.lat < 51.6264998528536

, это даже не учитывает lon.

Если вы хотите использовать пространственные способности, вы должны оставить свои места как Points, создать их индекс SPATIAL и использовать MBRContains для фильтрации ограничивающей рамки:

ALTER TABLE places ADD place_point GEOMETRY

CREATE SPATIAL INDEX sx_places_points ON places (place_point)

UPDATE  places
SET     place_point = Point(lat, lon)

SELECT  *
FROM    places
WHERE   MBRContains(LineString(Point(51.3373, -1.1330), Point(51.6264, -0.6687)), place_point)
        AND -- do the fine filtering here

Обновление:

CREATE TABLE t_spatial (id INT NOT NULL, lat FLOAT NOT NULL, lon FLOAT NOT NULL, coord GEOMETRY) ENGINE=MyISAM;

INSERT
INTO    t_spatial (id, lat, lon)
VALUES  (1, 52.2532, 20.9778);

UPDATE  t_spatial
SET     coord = Point(lat, lon);

Это работает для меня в 5.1.35.

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