具有距离性能的MYSQL地理搜索

use*_*096 6 mysql geospatial bounding-box

我在我的网站上有一个mysql select语句,当网站变得非常繁忙时会出现性能问题.下面的查询从具有超过100k记录的表中搜索广告,在给定的lat和lon的25英里内并按距离排序.用户选择的里程数可以不同.

问题是我认为它很慢,因为它对表中的所有记录进行计算,而不是在纬度和离子25英里范围内进行计算.是否可以修改此查询,以便where子句仅选择25英里内的广告?我已经阅读了有关边界框和空间索引的内容,但我不知道如何将它们应用于此查询,我是否需要添加一个where子句来选择距离纬度25英里半径的记录,我该怎么做?

SELECT 
    adverts.*, 
    round(sqrt((((adverts.latitude - '53.410778') * (adverts.latitude - '53.410778')) * 69.1 * 69.1) + ((adverts.longitude - '-2.97784') * (adverts.longitude - '-2.97784') * 53 * 53)), 1) as distance
FROM 
    adverts
WHERE 
    (adverts.type_id = '3')
HAVING 
    DISTANCE < 25
ORDER BY 
    distance ASC 
LIMIT 120,10
Run Code Online (Sandbox Code Playgroud)

编辑:更新以包含表模式,请注意表更复杂,查询也是如此,但我已删除了此问题不需要的内容.

CREATE TABLE `adverts` (
`advert_id` int(10) NOT NULL AUTO_INCREMENT,
`type_id` tinyint(1) NOT NULL,
`headline` varchar(50) NOT NULL,
`description` text NOT NULL,
`price` int(4) NOT NULL,
`postcode` varchar(7) NOT NULL,
`latitude` float NOT NULL,
`longitude` float NOT NULL,
PRIMARY KEY (`advert_id`),
KEY `latlon` (`latitude`,`longitude`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
Run Code Online (Sandbox Code Playgroud)

当我对mysql语句做一个解释时,行数设置为67900,这比25英里半径的要多得多,而且额外设置为"使用where;使用filesort".

查询需要0.3秒,这非常慢,特别是当网站每秒获得大量请求时.

Kil*_*awr 6

有几种方法可以加快您的查询速度,我个人会利用该POW功能。

返回XY 次幂值。

手动乘法会减慢大型表的查询速度,尽管会获得相同的结果。

SELECT a .* , 
    round( sqrt( 
        (POW( a.latitude -'53.410778', 2)* 68.1 * 68.1) + 
        (POW(a.latitude -'-2.97784', 2) * 53.1 * 53.1) 
     )) AS distance
 FROM adverts a
     WHERE a.type_id = 3
     HAVING distance < 25
     LIMIT 0 , 30
Run Code Online (Sandbox Code Playgroud)

上面的查询在0.0008 sec带有10,000记录的表架构上运行(您的查询在相同的表架构上进行了测试0.0129 sec),因此性能有了相当大的提高。

其他优化技巧

  • 如果在 SELECT 语句中使用实际的列名而不是 .sql 查询会变得更快*
  • 完全引用表名mydatabase.mytable
  • 如果您必须ORDER BY使用primary key(它是一个indexed字段,或index在您打算使用的字段上创建一个ORDERING)。
  • 使用 mysql 框架函数进行数学计算将加快进程。
  • 最后尝试通过这些步骤使您的查询尽可能简单(越简单越快)。

来源


dou*_*arp 6

最快的方法是使用MySQL的地理空间扩展,这应该很容易,因为您已经在使用MyISAM表.这些扩展的文档可以在这里找到:http://dev.mysql.com/doc/refman/5.6/en/spatial-extensions.html

添加带有POINT数据类型的新列:

ALTER TABLE `adverts` 
ADD COLUMN `geopoint` POINT NOT NULL AFTER `longitude`
ADD SPATIAL KEY `geopoint` (`geopoint`)
Run Code Online (Sandbox Code Playgroud)

然后,您可以从现有的纬度和经度字段填充此列:

UPDATE `adverts` 
SET `geopoint` = GeomFromText(CONCAT('POINT(',`latitude`,' ',`longitude`,')'));
Run Code Online (Sandbox Code Playgroud)

下一步是根据将在WHERE子句中用作CONTAINS约束的输入纬度和经度创建边界框.您需要POINT根据所需的搜索区域和给定的起点确定一组适合您要求的X,Y 坐标.

您的最终查询将搜索搜索中的所有POINT数据POLYGON,然后您可以使用距离计算来进一步优化和排序数据:

SELECT a.*, 
    ROUND( SQRT( ( ( (adverts.latitude - '53.410778') * (adverts.latitude - '53.410778') ) * 69.1 * 69.1 ) + ( (adverts.longitude - '-2.97784') * (adverts.longitude - '-2.97784') * 53 * 53 ) ), 1 ) AS distance
FROM adverts a
WHERE a.type_id = 3
AND CONTAINS(a.geopoint, GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))'))
HAVING distance < 25
ORDER BY distance DESC
LIMIT 0, 30
Run Code Online (Sandbox Code Playgroud)

请注意,GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))')上面的内容不起作用,您需要在搜索开始周围用有效点替换坐标.如果您希望lat/long更改,则应考虑使用触发器来保持POINT数据和关联的SPATIAL KEY最新状态.对于大型数据集,您应该看到在计算每条记录的距离和使用HAVING子句过滤方面的性能大大提高.我个人定义了用于确定距离和创建边界的函数POLYGON.