9

我有一个 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 秒,这真的很慢,尤其是当网站每秒收到大量请求时。

4

2 回答 2

8

最快的方法是使用 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约束。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

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

于 2012-09-22T04:11:37.877 回答
6

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

返回X的Y 次幂的值。

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

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

来源

于 2012-09-19T12:55:44.423 回答