2009-08-26 2 views
4

레일즈 애플리케이션에서 geokit (acts_as_mappable)을 사용하고 있습니다. 많은 수의 모델이있을 때 방사형 또는 경계 검색 성능이 크게 떨어졌습니다 (1-2million으로 시도했지만 문제는 의심 할 여지가 없습니다) 이것보다).mysql이 내 인덱스를 사용하고 있는지 여부와 geokit의 성능을 향상시킬 수 있습니까?

Geokit은 테이블의 위도와 경도 (위도와 경도)를 기반으로 모든 계산을 수행합니다. 성능을 향상시키기 위해 일반적으로 위도와 경도에 결합 된 인덱스를 사용하여 성능을 향상시키려는 의도로 경계 상자에 'where'절을 추가합니다. 그러나 여전히 많은 수의 모델로 인해 매우 느리며 테두리 상자 절이 훨씬 더 도움이 될 것 같습니다.

내 질문에, 거기에 방법을 더 나은 결합 된 위도/lng 인덱스를 사용하거나 그렇지 않으면 geokit SQL 쿼리의 성능을 향상시킬 수 있습니까? 또는 lat/lng에 대한 조합 된 인덱스가 더 도움이 될 수 있습니까?

편집 : 지금 레일이 작업을 가지고 더 상세하게 솔루션을 작성했습니다 here 배경 예를 들어

,이 쿼리는 주어진 10 마일 이내에 모든 장소를 찾아

더 포인트. (내가 얼마나 많은 결과가 돌아 왔는지를 결정하기 위해 .length를 추가했다. geokit에서 이것을 말하는 더 좋은 방법이 있지만 좀 더 일반적인 SQL 쿼리를 강제하고 싶다.)

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

Mac mini에는 약 14 초가 걸립니다. 여기에 그래서 MySQL은 87,554 결과에서 장소의 수는 1,135하더라도 행을 검사하는 계획을

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 | 
+----+-------------+--------+-------+-----------------------------+-----------------------------+---------+------+-------+----------+-------------+ 

설명이다 (실제로 경계 상자에 자릿수 단지 1323)입니다. ([: 위도 : LNG] 장소 레일 이주 add_index 등으로 이루어진다)

| 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 | 
    +----+-------------+--------+-------+-----------------------------+-----------------------------+---------+------+-------+----------+-------------+ 

답변

3

일반용 B-Tree 인덱스는 이와 같은 쿼리에 적합하지 않습니다. 당신의 쿼리

range 접근 방법은 다음의 조건에 사용됩니다
places.lat > 51.3373601471464 AND places.lat < 51.6264998528536 

,이 경우에도 계정으로 lon을지지 않습니다.

당신은 경계 상자 필터링, 당신이 그들의 SPATIAL 인덱스를 생성, Points로 장소를 유지해야 공간적 능력을 사용하고 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에서 작동합니다.

+0

재미 있습니다.이 경우 어떤 색인이 있어야합니까? – frankodwyer

+0

감사합니다 - 그것은 훨씬 더 잘 작동하고 나는 그것을 밖으로 시도 할 것 같은데. 공간을 사용하지 않고 현재 쿼리를 향상시킬 수있는 방법이 있습니까 (geokit은 현재 mysql spatial stuff를 사용하지 않습니다)? 이 쿼리를 실행하면 흥미롭게도 – frankodwyer

+0

WHERE ((places.lat> 51.3373601471464 AND places.lat <51.6264998528536)); 42078 행만 반환합니다! 그래서 mysql이 그 부분을 잘 수행하지 못하는 것 같습니다. – frankodwyer

관련 문제