2009-08-26 11 views
4

Ich verwende geokit (acts_as_mappable) in einer Rails-Anwendung, und die Leistung von Radial- oder Grenzen-Suchen verschlechtert sich erheblich, wenn es eine große Anzahl von Modellen gibt (Ich habe es mit 1-2 Millionen versucht, aber das Problem tritt wohl früher auf.) als das).Verwendet MySQL meinen Index oder nicht, und kann die Leistung von Geokit verbessert werden?

Geokit berechnet alle Berechnungen basierend auf Lat- und lng-Spalten in der Tabelle (Längen- und Breitengrad). Um die Leistung zu verbessern, fügt geokit in der Regel eine where-Klausel für die Bounding Box hinzu, mit der Absicht, einen kombinierten Index für den Längen- und Breitengrad zu verwenden, um die Leistung zu verbessern. Es ist jedoch immer noch unglaublich langsam mit einer großen Anzahl von Modellen, und es scheint mir, dass die Bounding-Box-Klausel viel mehr helfen sollte als sie.

Also meine Frage ist, gibt es eine Möglichkeit, mysql besser nutzen die kombinierte lat/lng-Index oder auf andere Weise verbessern die Leistung von geokit SQL-Abfragen? Oder kann der kombinierte Index für lat/lng hilfreicher sein?

edit: Ich habe diese Arbeit mit Schienen bekommt jetzt und die Lösung im Detail geschrieben here

Mehr Hintergrund

Zum Beispiel findet diese Abfrage alle Orte im Umkreis von 10 Meilen von einem bestimmten Punkt. (Ich habe .length nur hinzugefügt, um festzustellen, wie viele Ergebnisse zurückkommen - es gibt bessere Möglichkeiten, dies in geokit zu sagen, aber ich wollte eine typischere SQL-Abfrage erzwingen).

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

Es dauert ungefähr 14s auf einem Mac mini. Hier ist der Plan

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

erklären Also mysql prüft 87.554 Zeilen, obwohl die Zahl der Plätze im Ergebnis 1135 (und die Anzahl der Plätze tatsächlich in dem Begrenzungsrahmen ist nur 1323).

Dies sind die Statistiken auf den Index (die mit einer Schienen Migration add_index gemacht: Orte, [: 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  |   | 

Nor es die verwandt zu sein scheint trig-Berechnungen, wie eine ähnliche Abfrage für einen Begrenzungsrahmen ergibt eine viel einfachere Abfrage zu tun, aber es führt ähnlich schlecht:

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

Gibt einen ähnlichen Plan erklären:

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

Antwort

3

Plain B-Tree Indizes sind nicht so gut für die Abfragen wie folgt.

Für Ihre Abfrage, die range Zugriffsverfahren auf den folgenden Bedingungen verwendet wird:

places.lat > 51.3373601471464 AND places.lat < 51.6264998528536 

, das sich nicht einmal lon Rechnung trägt.

Wenn Sie möchten, räumliche Fähigkeiten nutzen, sollten Sie Ihre Orte wie Points halten, erstellen Sie einen SPATIAL Index von ihnen und verwenden MBRContains den Begrenzungsrahmen zu filtern:

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 

Update:

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); 

Dies funktioniert für mich in 5.1.35.

+0

Das ist interessant - welche Art von Index sollte es in diesem Fall geben? – frankodwyer

+0

danke - das klingt, als würde es viel besser funktionieren und ich werde es ausprobieren. Gibt es auch eine Möglichkeit, die aktuelle Abfrage zu verbessern, ohne spatial zu verwenden (da Geokit derzeit keine mysql-Spaces verwendet)? – frankodwyer

+0

Interessanterweise, wenn ich diese Abfrage ausführen SELECT * FROM 'Orte' WHERE ((places.lat> 51.3373601471464 AND places.lat <51.6264998528536)); es gibt nur 42078 Zeilen zurück! Es sieht also so aus, als würde mysql diesen Teil auch nicht gut machen. – frankodwyer