mysql - 需要帮助优化 mysql 的纬度/经度地理搜索

标签 mysql performance optimization geolocation query-optimization

我有一个 mysql (5.0.22) myisam 表,其中大约有 300k 条记录,我想在五英里半径内进行纬度/经度距离搜索。

我有一个涵盖经纬度字段的索引,当我只选择纬度/经度时速度很快(毫秒级响应)。但是,当我选择表中的其他字段时,速度会减慢到 5-8 秒。

我正在使用 myisam 来利用全文搜索。其他索引表现良好(例如 select * from Listing where 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;

解释没有地理搜索的计划:

    +----+-------------+------------+-------+-----------------+-----------------+---------+------+------+-------------+
    | 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 |
    +----+-------------+------------+-------+-----------------+-----------------+---------+------+------+-------------+

用地理搜索解释计划:

+----+-------------+------------+-------+-----------------+-----------------+---------+------+------+-----------------------------+
| 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 |
+----+-------------+--------+-------+---------------+---------------+---------+------+--------+------------------------------------------+

谢谢!

最佳答案

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

出于以下原因,我(暂时)放弃了使用 MySQL 处理地理空间数据:

  • MySQL 仅支持 MyISAM 表上的空间数据类型/空间索引,具有 MyISAM 的固有缺点(关于事务、引用完整性...)
  • MySQL 实现了一些 OpenGIS 仅基于 MBR 的规范 (最小边界矩形)这是 对于最严重的人来说毫无用处 地理空间查询处理(见 this link in the MySQL manual ).您迟早会需要其中的某些功能。

具有适当 (GIST) 空间索引和适当查询的 PostgreSQL/Postgis 可以非常快。

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

关于mysql - 需要帮助优化 mysql 的纬度/经度地理搜索,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/951923/

相关文章:

php - 如何执行mysql命令DELIMITER

php - 将 MySQL 'TIME' 字段拆分为单独的部分

php - 如何通过来自 php 的变量更改 jQuery 中的数值

C# QuickSort 太慢

c++ - gcc/C++ : If CPU load is low, 那么代码优化没什么用,对吗?

php - 内存不足总是在同一行

PHP - IE 不显示来自数据库的更新值。 Chrome、FF、Opera - 确定

mysql - 如何提高大型 InnoDB 表的 DELETE FROM 性能?

c# - 如何从构建过程中删除 App_LocalResources

mysql - 临时表(Memory Engine)和大物理表(1.7GB myisam)的join优化