Использует ли mysql мой индекс или нет, и можно ли улучшить производительность geokit?
-
20-09-2019 - |
Вопрос
Я использую geokit (acts_as_mappable) в приложении для рельсов, и производительность поиска по радиусу или границам значительно снижается при наличии большого количества моделей (я пробовал с 1-2 миллионами, но проблема, несомненно, начинается раньше, чем это ).
Geokit выполняет все расчеты на основе столбцов широты и долготы в таблице (широта и долгота).Для повышения производительности в geokit обычно добавляется предложение «где» в ограничительной рамке с целью использования комбинированного индекса широты и долготы для повышения производительности.Однако при большом количестве моделей он по-прежнему невероятно медленный, и мне кажется, что предложение ограничивающей рамки должно помочь гораздо больше, чем сейчас.
Итак, мой вопрос: есть ли способ заставить MySQL лучше использовать комбинированный индекс широты и долготы или иным образом повысить производительность SQL-запросов Geokit?Или можно ли сделать комбинированный индекс широты и долготы более полезным?
редактировать:Теперь я работаю с рельсами и написал решение более подробно. здесь
Дополнительная информация
Например, этот запрос находит все места в пределах 10 миль от заданной точки.(Я добавил .length только для того, чтобы определить, сколько результатов возвращается — в geokit есть более удобные способы сказать это, но я хотел использовать более типичный SQL-запрос).
Place.find(:all,:origin=>latlng,:within=>10).length
На Mac Mini это занимает около 14 секунд.Вот план объяснения
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 :places, [: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 |
+----+-------------+--------+-------+-----------------------------+-----------------------------+---------+------+-------+----------+-------------+
Решение
Простой 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
.