4

Rails アプリケーションで geokit (acts_as_mappable) を使用していますが、多数のモデルがある場合、ラジアル検索または境界検索のパフォーマンスが大幅に低下します (私は 100 万から 200 万で試しましたが、問題は間違いなくこれよりも早く発生します) )。

Geokit は、テーブルの緯度と経度の列 (緯度と経度) に基づいてすべての計算を行います。パフォーマンスを向上させるために、ジオキットは通常、境界ボックスの「where」句を追加します。これは、パフォーマンスを向上させるために緯度と経度の組み合わせインデックスを使用することを目的としています。ただし、多数のモデルでは依然として非常に低速であり、バウンディング ボックス句はそれよりもはるかに役立つはずです。

私の質問は、mysql で lat/lng インデックスをより有効に活用したり、gekit sql クエリのパフォーマンスを向上させたりする方法はありますか? または、緯度/経度の複合インデックスをより役立つものにすることはできますか?

編集:私はこれをレールで動作させ、ソリューションをより詳細にここに書きました

その他の背景

たとえば、次のクエリは、特定の地点から 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 | 
+----+-------------+--------+-------+-----------------------------+-----------------------------+---------+------+-------+----------+-------------+

したがって、結果の桁数が 1135 であっても、mysql は 87554 行を調べています (境界ボックス内の実際の桁数はちょうど 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 | 
    +----+-------------+--------+-------+-----------------------------+-----------------------------+---------+------+-------+----------+-------------+
4

1 に答える 1

3

単純B-Treeなインデックスは、このようなクエリにはあまり適していません。

クエリではrange、次の条件でアクセス メソッドが使用されます。

places.lat > 51.3373601471464 AND places.lat < 51.6264998528536

lon、これも考慮されていません。

空間機能を使用する場合は、場所を のままにして、それらのインデックスをPoints作成し、それを使用して境界ボックスをフィルタリングする必要があります。SPATIALMBRContains

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

于 2009-08-26T11:17:25.683 に答える