MySQL 查询计算两个坐标之间的距离但使用 HAVING 子句太慢
MySQL query to calculate distance between two coordinates but too slow with HAVING clause
我有以下查询计算两个坐标之间的距离。但是,我想 select n
公里范围内的用户。比方说,我想 select 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
子句并在 WHERE
子句中将其替换为 AND a.id = :user
。截至目前,返回的结果与 GROUP BY
.
相同
做你想做的事情的方法是在 where
部分
中重复你的 select
上的代码
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
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
基本的答案是您无法提高查询的效率。对于你的方法,你基本上需要计算所有用户对之间的距离,这是昂贵的。
您可能会使用一些技巧。首先,您可能不需要反转对,因此可以将 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 n
公里范围内的用户。比方说,我想 select 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
子句并在 WHERE
子句中将其替换为 AND a.id = :user
。截至目前,返回的结果与 GROUP BY
.
做你想做的事情的方法是在 where
部分
select
上的代码
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
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
基本的答案是您无法提高查询的效率。对于你的方法,你基本上需要计算所有用户对之间的距离,这是昂贵的。
您可能会使用一些技巧。首先,您可能不需要反转对,因此可以将 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