【问题标题】:Need help optimizing a lat/Lon geo search for mysql需要帮助优化 mysql 的 lat/Lon 地理搜索
【发布时间】:2010-10-31 10:00:15
【问题描述】:

我有一个 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章使用哪里;使用索引;使用文件排序 | +----+-------------+--------+--------+------------- --+----------------+----------+------+--------+-- -------------------------------------------------+

谢谢!

【问题讨论】:

  • 发布快慢查询的解释计划。
  • 看起来您在该表中有太多列。您可以通过稍微标准化您的数据结构来从查询中挤出一些性能。 :)

标签: mysql performance optimization geolocation query-optimization


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

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

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

    【讨论】:

    • 你知道我在哪里可以找到关于平方距离计算的更多信息吗?
    • @Jeff:我会把它添加到答案中。
    • 这里有一个简单的建议:将您的大圆距离计算定义为一个存储函数,并重新编写您的查询,使其成为您的 lat 和 long 之后的第三个 AND 子句。先做你的纬度查询,然后是你的长查询。向北或向南一海里大约是全世界的一个纬度,但一个经度的距离因纬度而异。
    【解决方案3】:

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

    由于以下原因,我已经(暂时)放弃了使用 MySQL 来获取地理空间数据:

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

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

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

    【讨论】:

    • +1,是的,这是一个空间问题,所以它也需要空间解决方案。
    【解决方案4】:

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

    的视图

    Listing1Id、Listing2ID、距离

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

    然后你可以这样做:

    从 v_Distance d 中选择 Listing2ID 其中距离

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

      • 您已经接近与覆盖索引相关的问题了。我的索引没有涵盖足够的列。我将它扩展为涵盖所有需要的列,它更快,但仍然需要 1 - 1.7 秒。如果我想包含电话、电子邮件、网站等信息,我还必须将字符集减少为 latin1。(utf8 为 1 字节 vs 3 字节)
      猜你喜欢
      • 2013-12-26
      • 1970-01-01
      • 2011-09-15
      • 2011-08-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多