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 |
+----+-------------+--------+-------+-----------------------------+-----------------------------+---------+------+-------+----------+-------------+
Das ist interessant - welche Art von Index sollte es in diesem Fall geben? – frankodwyer
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
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