优化SQL距离查询

时间:2010-03-14 09:52:18

标签: sql mysql

我正在运行一个MySQL查询,它根据位置返回结果。但是我最近注意到它真的放慢了我的PHP应用程序。我使用CodeIgniter,探查器显示查询需要4.2秒。 geoname表有500,000行。我在关键列上有一些索引,还有什么可以加快这个查询?

这是我的SQL:

SELECT `products`.`product_name`
     , `geoname`.`geonameid`
     , `geoname`.`latitude`
     , `geoname`.`longitude`
     , `products`.`product_id`
     , AVG(ratings.vote) as rating
     , count(comments.comment_id) as total_comments
     ,   (6371 * acos(cos(radians(38.7666667)) 
               * cos(radians(geoname.latitude)) 
               * cos(radians(geoname.longitude) - radians(-3.3833333)) 
             +   sin(radians(38.7666667)) 
               * sin(radians(geoname.latitude)))
         ) AS distance
FROM (`foods`)
JOIN `geoname` ON `geoname`.`geonameid` = `products`.`geoname_id`
LEFT JOIN `ratings` 
  ON `ratings`.`var_id` = `products`.`product_id`
LEFT JOIN `comments` 
  ON `comments`.`var_id` = `products `.`product_id`
WHERE `products`.`product_id` != 82
GROUP BY `products`.`product_id`
HAVING `distance` < 99
ORDER BY `distance`
LIMIT 10

4 个答案:

答案 0 :(得分:3)

让我们从查询本身开始 cos(radians(geoname.latitude))和其他函数看起来像一个不变量,所以我们可以做一些预处理并将计算出的值存储在表中。 (计算触发函数主要涉及使用昂贵的系列扩展)。

6371 * acos(cos(弧度(38.7666667)) - 这等于弧度(38.76667)* 6371所以我们为什么呢?它的成本。

其次,如果你不太关心精度你可以预先计算弧度本身,让我们说从0到pi / 2的10000点 - 这应该给出一个很好的近似值,最多4个十进制数,例如小于1 km < / p>

(6371 * acos(cos(radians(38.7666667))
 * cos(radians(geoname.latitude))
 * cos(radians(geoname.longitude) - radians(-3.3833333))
+ sin(radians(38.7666667))
* sin(radians(geoname.latitude))))

还记得sin(a)当a&gt; pi / 2和a&lt; pi等于罪(pi - a) 当a> pi和a&lt; 3/2 pi等于-sin(a-pi)并且当a> 1时3/2 pi和a&lt; 2pi它等于-sin(2pi-a)。可以为cos函数制作类似的函数。

试试这个,看看是否有帮助。 路加

答案 1 :(得分:0)

如果你问MySQL解析计划,我想你会发现距离计算会使你的索引变得无用。您正在强制查询引擎执行TABLE SCAN。

保存情况的唯一方法是将距离放在单独的列中并将其编入索引。

答案 2 :(得分:0)

如果您可以将任何搜索位置近似,例如1000个空间中的10000点,您实际上可以按照以下方式在辅助表中存储距离:

create table distance (
position1_id int,
position2_id int,
distance int -- probably precise enough
)

带有position1_id和距离的索引。该表可以有10 ^ 6到10 ^ 8行,但是使用索引数据,我想你可以快速检索最近的position2_id。即使这对你来说不够精确(因为必须达到有限的分辨率),它也可以让你快速消除在特定情况下你不关心的大约99%的位置。

答案 3 :(得分:0)

你可以通过简单地除以57.29577951来激活radians()函数。这将消除每行六次数学计算。对于大型集合上的关系查询连接,该公式通常不友好。尽管如此,这是一个不同的查询,试图在加入之前缩小视图。我不确定它是否会在没有测试和调整的情况下运行得更快或更慢。最终,我决定在主键上构建一个统计表,并在其他表上配置触发器来维护它,这样你的最终距离计算查询就会立即针对一个非常小的表运行。为了真正令人敬畏,我将建立一个类似于统计表的审计表来总结趋势。

select p.product_name,
g.geonameid,
g.latitude,
g.longitude,
p.product_id,
avg(r.votes) as rating,
c.total_comments,
g.distance
(select product_id, geoname_id, product_name from products where product_id != 82) p
inner join 
(select geonameid, latitude, longitude, (6371 * acos(cos(38.7666667/57.29577951) 
               * cos(latitude/57.29577951) 
               * cos((longitude/57.29577951) - (-3.3833333/57.29577951)) 
             +   sin(38.7666667/57.29577951) 
               * sin(latitude/57.29577951))
         ) AS distance
from geoname group by geonameid having distance < 99) g on p.geoname_id = g.geonameid
left join
(select var_id, count(vote) votes from ratings group by var_id) r on p.product_id = r.var_id
left join 
(select var_id, count(comment_id) total_comments from comments group by var_id) c on p.product_id = c.var_id
group by p.product_id  
order by g.distance
limit 10