MySQL query performance tuning
I'm trying to run a query over a table with around a 1 million records. The structure of the table with its indexes is:
CREATE TABLE `table` (
`Id` int(11) NOT NULL,
`Name` varchar(510) DEFAULT NULL,
`Latitude` float NOT NULL DEFAULT '0',
`Longitude` float NOT NULL DEFAULT '0',
PRIMARY KEY (`Latitude`,`Longitude`,`Id`),
KEY `IX_Latitude_Longitude` (`Latitude`,`Longitude`),
KEY `IX_Latitude` (`Latitude`),
KEY `IX_Longitude` (`Longitude`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
I'm running the following query:
SELECT m.Id, m.Name, sqrt(69.1 * (m.Latitude - :latitude) * 69.1 * (m.Latitude - :latitude) +
53.0 * (m.longitude - :longitude) * 53.0 * (m.longitude - :longitude)) as Distance,
m.Latitude as Latitude, m.Longitude as Longitude
FROM table m
WHERE sqrt(69.1 * (m.Latitude - :latitude) * 69.1 * (m.Latitude - :latitude)
+ 53.0 * (m.longitude - :longitude) * 53.0 * (m.longitude - :longitude)) < :radius
ORDER BY sqrt(69.1 * (m.Latitude - :latitude) * 69.1 * (m.Latitude - :latitude) +
53.0 * (m.longitude - :longitude) * 53.0 * (m.longitude - :longitude)) desc
LIMIT 0, 100
That suppose to return all the records in a specific radius (distance calculation information: http://www.meridianworlddata.com/Distance-Calculation.asp)
BUT the query takes a lot of time... Here is the explain plan that I get:
id|select_type |table|type|possible_keys|key |key_len|ref |rows |Extra
1 |SIMPLE |m |ALL |{null} |{null}|{null} 开发者_开发百科|{null}|1264001|Using where; Using filesort
What am I doing wrong? Which index do I need to add in order to cause the query to use it instead of table scan? Do I need to change the table structure?
You're using functions inside your WHERE clause, so it will always result in a table scan. The database has no way to index against the result of a function. I think your best option is to come up with some way to limit the results before trying to evaluate the distance algorithm.
For instance, for a given location, you can know the minimum and maximum possible latitude that can fall within your set distance, so filter by that first. A degree of latitude is ~69 miles, so if your search radius is 50 miles, it would never be possible for anything more than 0.725 degrees of latitude away to fall within 50 miles of your location. Since this is just a numeric comparison WHERE m.latitude > (:latitude - 0.725) AND m.latitude < (:latitude + 0.725)
, not a call to a function, the database will be able to use your indexes to evaluate it.
Longitude is more complicated, since the distance for each degree varies depending on how far north/south the location happens to be, but depending on how much work you want to put into it, you could do the same with longitude as well.
精彩评论