需要帮助优化lat / Lon geo搜索mysql

时间:2009-06-04 17:19:31

标签: mysql performance optimization geolocation query-optimization

我有一个mysql(5.0.22)myisam表,里面有大约300k的记录,我想在5英里范围内进行纬度/离子距离搜索。

我有一个覆盖lat / lon字段的索引,当我选择lat / lon时,它是快速的(毫秒响应)。但是当我选择表格中的其他字段时,可能会慢慢减慢到5-8秒。

我正在使用myisam来利用全文搜索。其他索引表现良好(例如,从列表中选择*,其中slug ='xxxxx')。

如何优化查询,表格或索引以加快速度?

我的架构是:

CREATE TABLE  `Listing` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `name` varchar(125) collate utf8_unicode_ci default NULL,
  `phone` varchar(18) collate utf8_unicode_ci default NULL,
  `fax` varchar(18) collate utf8_unicode_ci default NULL,
  `email` varchar(55) collate utf8_unicode_ci default NULL,
  `photourl` varchar(55) collate utf8_unicode_ci default NULL,
  `thumburl` varchar(5) collate utf8_unicode_ci default NULL,
  `website` varchar(85) collate utf8_unicode_ci default NULL,
  `categoryid` int(10) unsigned default NULL,
  `addressid` int(10) unsigned default NULL,
  `deleted` tinyint(1) default NULL,
  `status` int(10) unsigned default '2',
  `parentid` int(10) unsigned default NULL,
  `organizationid` int(10) unsigned default NULL,
  `listinginfoid` int(10) unsigned default NULL,
  `createuserid` int(10) unsigned default NULL,
  `createdate` datetime default NULL,
  `lasteditdate` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
  `lastedituserid` int(10) unsigned default NULL,
  `slug` varchar(155) collate utf8_unicode_ci default NULL,
  `aclid` int(10) unsigned default NULL,
  `alt_address` varchar(80) collate utf8_unicode_ci default NULL,
  `alt_website` varchar(80) collate utf8_unicode_ci default NULL,
  `lat` decimal(10,7) default NULL,
  `lon` decimal(10,7) default NULL,
  `city` varchar(80) collate utf8_unicode_ci default NULL,
  `state` varchar(10) collate utf8_unicode_ci default NULL,
  PRIMARY KEY  (`id`),
  KEY `idx_fetch` USING BTREE (`slug`,`deleted`),
  KEY `idx_loc` (`state`,`city`),
  KEY `idx_org` (`organizationid`,`status`,`deleted`),
  KEY `idx_geo_latlon` USING BTREE (`status`,`lat`,`lon`),
  FULLTEXT KEY `idx_name` (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=DYNAMIC;

我的查询是:

SELECT Listing.name, Listing.categoryid, Listing.lat, Listing.lon
, 3956 * 2 * ASIN(SQRT( POWER(SIN((Listing.lat - 37.369195) * pi()/180 / 2), 2) + COS(Listing.lat * pi()/180) * COS(37.369195 * pi()/180) * POWER(SIN((Listing.lon --122.036849) * pi()/180 / 2), 2) )) rawgeosearchdistance
FROM Listing
WHERE
    Listing.status = '2'
    AND ( Listing.lon between -122.10913433498 and -121.96456366502 )
    AND ( Listing.lat between 37.296909665016 and 37.441480334984)
HAVING rawgeosearchdistance < 5
ORDER BY rawgeosearchdistance ASC;

解释没有geosearch的计划:

    +----+-------------+------------+-------+-----------------+-----------------+---------+------+------+-------------+
    | id | select_type | table      | type  | possible_keys   | key             | key_len |ref | rows | Extra       |
    +----+-------------+------------+-------+-----------------+-----------------+---------+------+------+-------------+
    |  1 | SIMPLE      | Listing    | range | idx_geo_latlon  | idx_geo_latlon  | 19      | NULL |  453 | Using where |
    +----+-------------+------------+-------+-----------------+-----------------+---------+------+------+-------------+

用geosearch解释计划:

+----+-------------+------------+-------+-----------------+-----------------+---------+------+------+-----------------------------+
| id | select_type | table      | type  | possible_keys   | key             | key_len | ref  | rows | Extra                       |
+----+-------------+------------+-------+-----------------+-----------------+---------+------+------+-----------------------------+
|  1 | SIMPLE      | Listing    | range | idx_geo_latlon  | idx_geo_latlon  | 19      | NULL |  453 | Using where; Using filesort |
+----+-------------+------------+-------+-----------------+-----------------+---------+------+------+-----------------------------+

这是覆盖索引的解释计划。以正确的顺序排列列有很大的不同:

+----+-------------+--------+-------+---------------+---------------+---------+------+--------+------------------------------------------+
| id | select_type | table  | type  | possible_keys | key           | key_len | ref  | rows   | Extra                                    |
+----+-------------+--------+-------+---------------+---------------+---------+------+--------+------------------------------------------+
|  1 | SIMPLE      | Listing | range | idx_geo_cover | idx_geo_cover | 12      | NULL | 453     | Using where; Using index; Using filesort |
+----+-------------+--------+-------+---------------+---------------+---------+------+--------+------------------------------------------+

谢谢!

5 个答案:

答案 0 :(得分:4)

我认为你真的应该考虑使用 PostgreSQL (结合Postgis)。

我已经放弃了MySQL的地理空间数据(目前),原因如下:

  • MySQL仅支持MyISAM表上的空间数据类型/空间索引,具有MyISAM的固有缺点(关于事务,引用完整性......)
  • MySQL实现了一些OpenGIS 规格仅基于MBR (最小边界矩形)是 最严重的是没用 地理空间查询处理(见 this link in the MySQL manual)。有可能你很快就会需要这些功能。
具有正确(GIST)空间索引和正确查询的PostgreSQL / Postgis可以非常快。

示例:确定“小”选择的多边形与具有超过500万(!)个非常复杂多边形的表之间的重叠多边形,计算这些结果之间的重叠量+排序。平均运行时间:30到100毫秒(这个特定的机器有很多RAM当然。不要忘记调整你的PostgreSQL安装...(阅读文档)。

答案 1 :(得分:1)

您可能只在lat / lon查询中使用'覆盖索引'。当查询使用的索引包含您要选择的数据时,会出现覆盖索引。 MySQL只需要访问索引而不需要访问数据行。 See this for more info。这可以解释为什么lat / lon查询如此之快。

我怀疑计算和返回的行数,减慢了查询的时间。 (加上必须为having子句创建的任何临时表)。

答案 2 :(得分:0)

你真的应该避免在你的select语句中做那么多的数学运算。这可能是你减速很多的根源。请记住,SQL是一种查询语言;它实际上并没有针对三角函数进行优化。

如果您进行非常天真的距离搜索(将返回更多结果),然后将结果取消,SQL会更快,您的整体结果会更快。

如果您想在查询中使用距离,至少应使用平方距离计算; sqrt计算非常慢。平方距离更容易使用。平方距离计算只是使用距离的平方而不是距离;它简单得多。对于笛卡尔坐标系,由于直角三角形的短边的平方和等于斜边的平方,因此计算平方距离(只是两个平方的总和)比计算距离更容易;所有你需要做的就是确保你想要比较的距离(所以不是找到精确的距离并将其与你想要的距离进行比较(比方说5),你找到了方形距离,然后比较到所需距离的平方(25,如果你想要的距离是5)。

答案 3 :(得分:0)

根据您的商家信息的数量,您可以创建包含

的视图
  

listing1Id,Listing2ID,Distance

基本上只是让所有距离“预先计算”

然后你可以这样做:

  

从v_Distance d中选择listing2ID   距离&lt; 5和listing1ID =   XXX

答案 4 :(得分:0)

当我实现地理半径搜索时,我只是将所有美国Zipcodes加载到内存中,使用lat long,然后使用我的半径起点获取半径中的zipcodes列表,然后将其用于我的db查询。当然我使用solr进行搜索,因为搜索空间在2000万行范围内,但应该适用相同的原则。当我在手机上时,对这种反应的浅薄表示道歉。