您使用哪个公式计算距离并不重要。更重要的是您必须阅读、处理和排序的行数。在最好的情况下,您可以在 WHERE 子句中使用条件索引来限制处理的行数。您可以尝试对您的位置进行分类 - 但这取决于您的数据的性质,如果这会运作良好。您还需要找出要使用的“类别”。更通用的解决方案是使用SPATIAL INDEX和ST_Within()函数。
现在让我们运行一些测试..
在我的数据库(MySQL 5.7.18)中,我有下表:
CREATE TABLE `cities` (
`cityId` MEDIUMINT(9) UNSIGNED NOT NULL AUTO_INCREMENT,
`country` CHAR(2) NOT NULL COLLATE 'utf8mb4_unicode_ci',
`city` VARCHAR(100) NOT NULL COLLATE 'utf8mb4_unicode_ci',
`accentCity` VARCHAR(100) NOT NULL COLLATE 'utf8mb4_unicode_ci',
`region` CHAR(2) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
`population` INT(10) UNSIGNED NULL DEFAULT NULL,
`latitude` DECIMAL(10,7) NOT NULL,
`longitude` DECIMAL(10,7) NOT NULL,
`geoPoint` POINT NOT NULL,
PRIMARY KEY (`cityId`),
SPATIAL INDEX `geoPoint` (`geoPoint`)
) COLLATE='utf8mb4_unicode_ci' ENGINE=InnoDB
数据来自自由世界城市数据库,包含 3173958 (3.1M) 行。
注意geoPoint
是多余的,等于POINT(longitude, latitude)
。
考虑到用户位于伦敦的某个地方
set @lon = 0.0;
set @lat = 51.5;
并且您想从cities
表中找到最近的位置。
一个“琐碎”的查询将是
select c.cityId, c.accentCity, st_distance_sphere(c.geoPoint, point(@lon, @lat)) as dist
from cities c
order by dist
limit 1
结果是
988204 Blackwall 1085.8212159861014
执行时间:~ 4.970 秒
如果你使用不太复杂的函数ST_Distance()
,你会得到相同的结果,执行时间约为 4.580 秒——差别不大。
请注意,您不需要在表中存储地理点。你可以好好利用(point(c.longitude, c.latitude)
代替c.geoPoint
. 令我惊讶的是,它甚至更快(约 3.6 秒ST_Distance
,约 4.0 秒ST_Distance_Sphere
)。geoPoint
如果我根本没有专栏,它可能会更快。但这仍然无关紧要,因为您不希望用户等待,所以如果您可以做得更好,请登录以获得响应。
现在让我们看看如何将SPATIAL INDEX与ST_Within()
.
您需要定义一个包含最近位置的多边形。一种简单的方法是使用ST_Buffer(),它将生成一个具有 32 个点的多边形,并且几乎是一个圆*。
set @point = point(@lon, @lat);
set @radius = 0.1;
set @polygon = ST_Buffer(@point, @radius);
select c.cityId, c.accentCity, st_distance_sphere(c.geoPoint, point(@lon, @lat)) as dist
from cities c
where st_within(c.geoPoint, @polygon)
order by dist
limit 1
结果是一样的。执行时间约为 0.000 秒(这就是我的客户(HeidiSQL)所说的)。
* 请注意,@radius
以度数表示,因此多边形将更像椭圆而不是圆形。但是在我的测试中,我总是得到与简单而缓慢的解决方案相同的结果。在我在生产代码中使用它之前,我会调查更多的边缘情况。
现在您需要为您的应用程序/数据找到最佳半径。如果它太小 - 你可能得不到任何结果,或者错过最近的点。如果它太大 - 您可能需要处理太多行。
这是给定测试用例的一些数字:
- @radius = 0.001:没有结果
- @radius = 0.01:恰好一个位置(有点幸运) - 执行时间 ~ 0.000 秒
- @radius = 0.1:55 个位置 - 执行时间 ~ 0.000 秒
- @radius = 1.0:2183 个位置 - 执行时间 ~ 0.030 秒