【问题标题】:MYSQL Geo Search having distance performance具有距离性能的 MYSQL 地理搜索
【发布时间】:2012-09-11 17:29:12
【问题描述】:

我有一个 mysql select 语句用于在我的网站上进行搜索,当网站变得非常繁忙时,它会出现性能问题。下面的查询从具有超过 10 万条记录的表中搜索广告,在给定纬度和经度的 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

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

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

当我对 mysql 语句进行解释时,行数设置为 67900,这比 25 英里半径内的行数多得多,额外的设置为“使用 where;使用文件排序”。

查询需要 0.3 秒,这确实很慢,尤其是当网站每秒收到大量请求时。

【问题讨论】:

  • 我已经注意到这个查询有几个问题,我有一些关于如何让它更快的想法,你能给我们预览一下表模式吗? (你的主键等)

标签: mysql geospatial bounding-box


【解决方案1】:

最快的方法是使用 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`)

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

UPDATE `adverts` 
SET `geopoint` = GeomFromText(CONCAT('POINT(',`latitude`,' ',`longitude`,')'));

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

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

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

请注意,上面的GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))')不起作用,您需要将坐标替换为搜索起点周围的有效点。如果您预计纬度/经度会发生变化,您应该考虑使用触发器来保持POINT 数据和关联的SPATIAL KEY 是最新的。对于大型数据集,您应该会看到在计算每条记录的距离和使用HAVING 子句进行过滤方面性能得到了极大的提高。我个人定义了用于确定距离和创建边界的函数POLYGON

【讨论】:

  • 如何使用输入纬度/经度定义Polygon()?例如。从输入纬度/经度到每条边 25 英里的正方形,或半径为 25 英里且以输入纬度/经度为中心的圆的近似值,等等。
  • 这取决于您想要的准确度。赤道的每个经度度约为 54.6 英里,纬度的每个度数约为 69 英里,在两极接近 0。只需谷歌“使用纬度和经度计算边界框”即可查看有关该主题的文章。
【解决方案2】:

有几种方法可以加快查询速度,我个人会利用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

上述查询在0.0008 sec 的表架构上运行,记录为10,000(您在同一表架构上测试的查询采用0.0129 sec),因此性能显着提高。

其他优化提示

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

来源

【讨论】:

  • 谢谢,我不认为我可以在 where 子句中引用 a.DISTANCE,因为距离是在 select 中计算的,它实际上不是表中的字段。
  • 阅读更多内容后,我发现WHERE Clause 实际上无法访问user-defined 变量。我已经在适当的地方编辑了我的答案。问候
  • testing 在带有10,000 记录的表上更新了答案。
  • 您好,感谢您的更新,您的查询会更快,因为它没有按距离排序的子句,这会大大减慢查询速度,但这是必需的。不过,这仍然不能回答我原来的问题,如果表包含 100,000 条记录,则无论有没有声明,它都会对整个 100,000 条记录进行距离计算。我想更新查询,使其具有仅包含纬度和经度 25 英里范围内的记录的 where 子句。
  • 您不能在数据到达后对其进行排序(ORDER BY ASC)吗?出mysql?在您的应用程序语言中? (每种语言都有自己的排序方式pythonphpjavac++ 等等)
猜你喜欢
  • 2011-08-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-18
  • 1970-01-01
  • 2012-03-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多