【问题标题】:Database: Best performance way to query geo location data?数据库:查询地理位置数据的最佳性能方式?
【发布时间】:2010-12-21 06:30:54
【问题描述】:

我有一个 MySQL 数据库。我将房屋存储在数据库中,实际上只对数据库执行 1 次查询,但我需要以超快的速度执行此查询,即返回一个正方形内的所有房屋框地理纬度和经度。

SELECT * FROM homes 
WHERE geolat BETWEEN ??? AND ???
AND geolng BETWEEN ??? AND ???

对我来说存储地理数据的最佳方式是什么,以便我可以最快地执行在地理定位框中显示所有家的查询?

基本上:

  • 我是否使用最好的 SQL 语句最快地执行此查询?
  • 是否存在任何其他方法,甚至可能不使用数据库,让我在盒装地理位置范围内查询房屋结果的最快方式?

如果有帮助,我在下面包含了我的数据库表架构:

CREATE TABLE IF NOT EXISTS `homes` (
  `home_id` int(10) unsigned NOT NULL auto_increment,
  `address` varchar(128) collate utf8_unicode_ci NOT NULL,
  `city` varchar(64) collate utf8_unicode_ci NOT NULL,
  `state` varchar(2) collate utf8_unicode_ci NOT NULL,
  `zip` mediumint(8) unsigned NOT NULL,
  `price` mediumint(8) unsigned NOT NULL,
  `sqft` smallint(5) unsigned NOT NULL,
  `year_built` smallint(5) unsigned NOT NULL,
  `geolat` decimal(10,6) default NULL,
  `geolng` decimal(10,6) default NULL,
  PRIMARY KEY  (`home_id`),
  KEY `geolat` (`geolat`),
  KEY `geolng` (`geolng`),
) ENGINE=InnoDB  ;

更新

我知道空间会影响地球的曲率,但我最感兴趣的是最快返回地理数据。除非这些空间数据库包以某种方式更快地返回数据,否则请不要推荐空间扩展。谢谢

更新 2

请注意,以下没有人真正回答了这个问题。我真的很期待我可能得到的任何帮助。提前致谢。

【问题讨论】:

  • 我还推荐阅读 MySQL 的空间功能:dev.mysql.com/doc/refman/5.0/en/spatial-extensions.html
  • “数据不好”是什么意思?我的应用程序正在查看通常不超过 3 英里乘 3 英里宽的数据。所以地球的曲率并没有考虑那么多
  • 所有,空间并不快。我正在使用 InnoDB。根据文档“InnoDB 表在 MySQL 5.0.16 之前不支持空间数据类型。从 5.0.16 开始,InnoDB 支持空间数据类型,但不支持它们的索引。”dev.mysql.com/doc/refman/5.0/en/innodb-restrictions.html为什么在文档中你一直推荐 SPATIAL正在让我阅读,并声称我不是在阅读,而是上午,明确指出对于 InnoDB 数据库 - 没有索引会导致查询速度较慢。再一次,我手头的问题是如何对地理数据执行最快的查询?
  • 我不想听起来粗鲁,但是当您声称我没有阅读您链接的文档并且当我阅读它时,它清楚地说明了您声称的对立面,这令人沮丧。这让我想,您是否也在阅读您自己链接的文档?
  • UTM 会很尴尬,除非感兴趣区域的宽度小于大约 6 度经度并且最好只有赤道的一侧。如果区域比这更宽,您需要指定一个区域,并且坐标将在区域边界上不连续。在赤道,y 坐标从北方接近零,但从南方接近 10000000。对于经纬度都很大的地区,最简单的坐标系是经纬度。你只需要接受球坐标带来的问题。

标签: mysql database database-design performance


【解决方案1】:

有一篇关于 MySQL 地理定位性能的好论文here

EDIT 很确定这是使用固定半径。此外,我也不是 100% 确定计算距离的算法是最先进的(即它会“钻”穿地球)。

重要的是,该算法很便宜,可以为您提供执行适当距离搜索的行数限制。


该算法通过在源点周围的一个正方形中选取候选者进行预过滤,然后以 英里 为单位计算距离。

预先计算,或使用源建议的存储过程:

# Pseudo code
# user_lon and user_lat are the source longitude and latitude
# radius is the radius where you want to search
lon_distance = radius / abs(cos(radians(user_lat))*69);
min_lon = user_lon - lon_distance;
max_lon = user_lon + lon_distance;
min_lat = user_lat - (radius / 69);
max_lat = user_lat + (radius / 69);
SELECT dest.*,
  3956 * 2 * ASIN(
    SQRT(
      POWER(
        SIN(
          (user_lat - dest.lat) * pi() / 180 / 2
        ), 2
      ) + COS(
        user_lat * pi() / 180
      ) * COS(
        dest.lat * pi() / 180
      ) * POWER(
        SIN(
          (user_lon - dest.lon) * pi() / 180 / 2
        ), 2
      )
    )
  ) as distance
FROM dest
WHERE 
  dest.lon between min_lon and max_lon AND
  dest.lat between min_lat and max_lat
HAVING distance < radius
ORDER BY distance
LIMIT 10

【讨论】:

  • 看来使用幻灯片#14 上的存储过程很有希望,但我不清楚这是否假设为固定半径。你知道半径是否固定吗?我希望能够通过框角(半径)
  • 我需要能够将盒装半径作为参数传入。你认为我可以这样使用链接的文档吗?
【解决方案2】:

从 MySQL 5.7 开始,mysql 可以使用 ST_Distance_Sphere() 和 ST_Contains() 等地理索引来提高性能。

【讨论】:

    【解决方案3】:

    您可以考虑创建一个单独的表“GeoLocations”,该表的主键为 ('geolat','geolng'),并且如果该特定地理位置恰好有一个家,则该表的列中包含 home_id。这应该允许优化器搜索将在磁盘上排序的一系列地理位置以获得 home_ids 列表。然后,您可以与您的 'homes' 表执行连接,以查找有关这些 home_id 的信息。

    CREATE TABLE IF NOT EXISTS `GeoLocations` (
    `geolat` decimal(10,6) NOT NULL,
    `geolng` decimal(10,6) NOT NULL,
    `home_id` int(10) NULL
    PRIMARY KEY  (`geolat`,`geolng`)
    );
    
    SELECT GL.home_id
    FROM GeoLocations GL
    INNER JOIN Homes H
     ON GL.home_id = H.home_id
    WHERE GL.geolat between X and Y
     and GL.geolng between X and Y
    

    【讨论】:

      【解决方案4】:

      我遇到了同样的问题,并写了一篇由 3 部分组成的博文。这比地理索引要快。

      IntroBenchmarkSQL

      【讨论】:

      • Evert,你是如何实现 Morton(z 值)的?您是第二篇文章,只是跳入并没有说明您是如何计算该值的
      • 第三个确实如此。有一个存储过程
      • 我不明白的是,当我执行SELECT时,我怎么知道要选择的morton值是什么?
      • 好问题。您应该确保对于表中的每一行,您还存储了这个 morton 值。您可以使用 AFTER INSERT(连同 AFTER UPDATE)来执行此操作。当您选择时,您可以简单地在 getGeoMorton(lat1,lng1) 和 getGeoMorton(lat2,lng2) 之间进行操作。因为 morton select 将是一个近似值,并且可以包括该区域之外的许多项目,所以您还必须为纬度和经度边界框添加一个标准的 where 子句。真正的诀窍是,您现在将 BTREE 用于更小的区域,而不仅仅是纬度或经度。
      • 这就是为什么 SO 答案应该包含相关引号的原因......链接已失效。
      【解决方案5】:

      如果您确实需要提高性能,您可以为数据定义边界框,并在插入时将预计算的边界框映射到您的对象,并在以后使用它们进行查询。

      如果结果集相当小,您仍然可以在应用程序逻辑中进行精度校正(比数据库更容易水平扩展),同时能够提供准确的结果。

      查看 Bret Slatkin 的 geobox.py,其中包含有关该方法的大量文档。

      如果您打算在可预见的将来执行更复杂的查询,我仍然建议您检查 PostgreSQL 和 PostGIS 与 MySQL 相比。

      【讨论】:

      • 这正是我们不应该在 StackOverflow 上使用链接的原因。您的链接已损坏。
      • @Sandor 感谢您告诉我,我已经修改了答案并删除了死链接。
      【解决方案6】:

      我成功使用的一个技巧是创建四舍五入区域。也就是说,如果您有一个位于 36.12345,-120.54321 的位置,并且您想将其与半英里(大约)网格框内的其他位置分组,则可以将其区域称为 36.12x-120.54,并且具有相同四舍五入区域的所有其他位置将落在同一个框中。

      显然,这不会让您获得一个干净的半径,即如果您正在查看的位置比另一个边缘更靠近一个边缘。但是,通过这种设置,很容易计算出围绕您的主要位置框的八个框。也就是说:

      [36.13x-120.55][36.13x-120.54][36.13x-120.53]
      [36.12x-120.55][36.12x-120.54][36.12x-120.53]
      [36.11x-120.55][36.11x-120.54][36.11x-120.53]
      

      使用匹配的四舍五入标签提取所有位置,然后,一旦将它们从数据库中取出,您就可以进行距离计算以确定要使用的位置。

      【讨论】:

        【解决方案7】:

        一个很好的替代品是 MongoDB 及其 Geospatial Indexing

        【讨论】:

          【解决方案8】:

          坚持您当前的方法,您应该做出一项改变, 而不是分别索引 geolat 和 geolong 你应该有一个复合索引:

          KEY `geolat_geolng` (`geolat`, `geolng`),
          

          目前您的查询将只利用两个索引之一。

          【讨论】:

            【解决方案9】:

            家?你可能连一万个都没有。只需使用STRTree 之类的内存索引即可。

            【讨论】:

              【解决方案10】:

              这看起来很快。我唯一担心的是它会使用一个索引来获取纬度 3 英里内的所有值,然后过滤那些在经度 3 英里内的值。如果我了解底层系统的工作原理,那么每个表只能使用一个 INDEX,因此 lat 或 long 上的索引毫无价值。

              如果您有大量数据,它可能会加快处理速度,为每 1x1 平方英里提供一个唯一的逻辑 ID,然后对 SELECT 进行额外限制,即 (area="23234 /34234" OR area="23235/34234" OR ... ) 为您的点周围的所有正方形,然后强制数据库使用该索引而不是纬度和经度。然后,您将只过滤更少平方英里的数据。

              【讨论】:

              • 每个表一个索引?你把它和主键混淆了吗?
              • 我的意思是,当您执行 SELECT 时,它只使用 SELECT 中的每个表的一个索引。
              • 啊.. 说得很好,但是您认为创建复合索引会有所作为吗?
              • 一个(更复杂的)复合索引是空间索引的作用,如果有很多数据,它会更快。
              【解决方案11】:

              您使用的索引确实是 B 树索引,并且支持您的查询中的 BETWEEN 关键字。这意味着优化器能够使用您的索引在您的“盒子”中找到房屋。然而,这并不意味着它将始终使用索引。如果您指定的范围包含太多“命中”,则不会使用索引。

              【讨论】:

              • 那么,会使用 min_latitude >= ???最大纬度
              • 来自手册:这相当于表达式 (min
              • 如果“点击”过多,索引将不会被使用,这是什么意思?我不明白
              • 如果指定的区域包含太多记录,则不会使用索引。
              猜你喜欢
              • 2013-11-06
              • 2016-11-21
              • 2020-10-24
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-09-13
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多