MySQL查询计算两个坐标之间的距离但使用HAVING子句太慢

问题描述

我有以下查询来计算两个坐标之间的距离。但是,我想选择 n 公里范围内的用户。比如说,我想选择 100 公里范围内的所有用户。因此,我必须在这里使用 HAVING 子句。但是,我看到执行速度太慢了。即使只有两条记录,它也会非常缓慢地返回结果。我想知道一百万用户记录(将来)会发生什么。因此,我正在寻求对当前查询的优化,以提高运行效率和速度。

SELECT *,111.1111 *
  degrees(ACOS(LEAST(1.0,COS(radians(a.latitude)) 
  * COS(radians(b.latitude)) 
  * COS(radians(a.longitude) - radians(b.longitude)) 
  + SIN(radians(a.latitude))
  * SIN(radians(b.latitude))))) AS distance_in_km 
FROM users AS a
JOIN users AS b ON a.id <> b.id
WHERE b.id != :user AND a.id = :user 
HAVING distance_in_km < :maxdist 
LIMIT 30

更新

按照 Rick James 的建议,我已删除 GROUP BY 子句,并在 AND a.id = :user 子句中将其替换为 WHERE。到目前为止,这将返回与 GROUP BY 相同的结果。

解决方法

基本的答案是你不能让你的查询更有效率。对于您的方法,您基本上需要计算所有用户对之间的距离,这很昂贵。

您可能会使用一些技巧。首先,您可能不需要反向对,因此您可以将 a.id <> b.id 替换为 a.id < b.id。这将减少一半的工作。

您可以使用 where 子句来预过滤行。例如,在地球表面的大部分地区,纬度或经度相距超过 2 度的点相距超过 100 公里。 并非所有地方都如此。但这对您来说可能已经足够了。这允许你写:

where a.latitude between b.latitude - 2 and b.latitude + 2 and
      a.longitude between b.longitude - 2 and b.longitude + 2

如果您的用户分布广泛,这将节省大量三角函数。

然而,真正的解决方案是使用 MySQL 的 GIS 扩展。在 documentation 中可以开始了解这一点。

编辑:

SELECT *,111.1111 *
   DEGREES(ACOS(LEAST(1.0,COS(RADIANS(a.latitude))
         * COS(RADIANS(b.latitude))
         * COS(RADIANS(a.longitude) - RADIANS(b.longitude))
         + SIN(RADIANS(a.latitude))
         * SIN(RADIANS(b.latitude))))) AS distance_in_km
FROM users a JOIN
     users b
     ON a.id <> b.id
WHERE a.id = :user AND
      a.latitude between b.latitude - 2 and b.latitude + 2 and
      a.longitude between b.longitude - 2 and b.longitude + 2
HAVING distance_in_km < 100
,

做您想做的事情的方法是在 select 部分重复 where 上的代码

SELECT *,COS(RADIANS(a.Latitude))
         * COS(RADIANS(b.Latitude))
         * COS(RADIANS(a.Longitude) - RADIANS(b.Longitude))
         + SIN(RADIANS(a.Latitude))
         * SIN(RADIANS(b.Latitude))))) AS distance_in_km
FROM users AS a
  JOIN users AS b ON a.id <> b.id
WHERE
    111.1111 *
    DEGREES(ACOS(LEAST(1.0,COS(RADIANS(a.Latitude))
         * COS(RADIANS(b.Latitude))
         * COS(RADIANS(a.Longitude) - RADIANS(b.Longitude))
         + SIN(RADIANS(a.Latitude))
         * SIN(RADIANS(b.Latitude))))) < 100

其他选项将是(更好的“外观和感觉”,更差的性能)

with d as (
    SELECT *,111.1111 *
       DEGREES(ACOS(LEAST(1.0,COS(RADIANS(a.Latitude))
             * COS(RADIANS(b.Latitude))
             * COS(RADIANS(a.Longitude) - RADIANS(b.Longitude))
             + SIN(RADIANS(a.Latitude))
             * SIN(RADIANS(b.Latitude))))) AS distance_in_km
    FROM users AS a
      JOIN users AS b ON a.id <> b.id
)
select * from d
where d.distance_in_km > 100