4

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

我有一个涵盖纬度/经度字段的索引,当我只选择纬度/经度时,它的速度很快(毫秒响应)。但是当我选择表格中的其他字段时,速度会慢到 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;

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

    +----+-------------+------------+-------+--------- --------+-----------------+---------+------+------ +--------------+
    | 编号 | 选择类型 | 表| 类型 | 可能的键 | 关键 | key_len |参考 | 行 | 额外 |
    +----+-------------+------------+-------+--------- --------+-----------------+---------+------+------ +--------------+
    | 1 | 简单 | 上市 | 范围 | idx_geo_latlon | idx_geo_latlon | 19 | 空 | 第453章 使用位置 |
    +----+-------------+------------+-------+--------- --------+-----------------+---------+------+------ +--------------+

用地理搜索解释计划:

+----+-------------+------------+-------+--------- --------+-----------------+---------+------+------ +------------------------------+
| 编号 | 选择类型 | 表| 类型 | 可能的键 | 关键 | key_len | 参考 | 行 | 额外 |
+----+-------------+------------+-------+--------- --------+-----------------+---------+------+------ +------------------------------+
| 1 | 简单 | 上市 | 范围 | idx_geo_latlon | idx_geo_latlon | 19 | 空 | 第453章 使用哪里;使用文件排序 |
+----+-------------+------------+-------+--------- --------+-----------------+---------+------+------ +------------------------------+

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

+----+-------------+--------+--------+------------- --+----------------+----------+------+--------+-- -------------------------------------------------+
| 编号 | 选择类型 | 表| 类型 | 可能的键 | 关键 | key_len | 参考 | 行 | 额外 |
+----+-------------+--------+--------+------------- --+----------------+----------+------+--------+-- -------------------------------------------------+
| 1 | 简单 | 上市 | 范围 | idx_geo_cover | idx_geo_cover | 12 | 空 | 第453章 使用哪里;使用索引;使用文件排序 |
+----+-------------+--------+--------+------------- --+----------------+----------+------+--------+-- -------------------------------------------------+

谢谢!

4

5 回答 5

4

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

由于以下原因,我已经放弃了 MySQL 用于地理空间数据(现在):

  • MySQL 仅支持 MyISAM 表上的空间数据类型/空间索引,具有 MyISAM 的固有缺点(关于事务、引用完整性......)
  • MySQL 仅基于 MBR(最小边界矩形)实现了一些 OpenGIS 规范,这对于最严重的地理空间查询处理非常无用(请参阅 MySQL 手册中的此链接)。您可能迟早会需要其中的一些功能。

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

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

于 2009-06-04T17:56:27.273 回答
1

您可能在仅经纬度查询中使用了“覆盖索引”。当查询使用的索引包含您选择的数据时,就会出现覆盖索引。MySQL 只需要访问索引而不需要访问数据行。 请参阅此了解更多信息。这可以解释为什么纬度/经度查询如此之快。

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

于 2009-06-04T17:27:39.423 回答
0

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

如果您进行非常简单的距离搜索(这将返回更多结果)然后筛选您的结果,SQL 会更快,您的整体结果也会更快。

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

于 2009-06-04T17:23:44.607 回答
0

根据您的列表的数量,您可以创建一个包含

Listing1Id, Listing2ID, 距离

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

然后你可以做类似的事情:

从 v_Distance d 中选择 Listing2ID,其中距离 < 5 并且 Listing1ID = XXX

于 2009-06-04T17:43:22.440 回答
0

当我实现地理半径搜索时,我只是将我们所有的邮政编码及其 lat long 加载到内存中,然后使用我的起点半径来获取半径中的邮政编码列表,然后将其用于我的数据库查询。当然,我使用 solr 进行搜索,因为搜索空间在 2000 万行范围内,但应该适用相同的原则。抱歉,当我在打电话时,这个回应的肤浅。

于 2009-06-04T23:13:24.200 回答