如何在普通mysql中进行地理空间搜索
Posted
技术标签:
【中文标题】如何在普通mysql中进行地理空间搜索【英文标题】:How to do geo spatial searching in plain mysql 【发布时间】:2021-09-26 09:55:30 【问题描述】:给定一个southwest(lng, lat)
和northeast(lng,lat)
的边界框,我想找出落在这个给定区域内的所有点。该表目前设计如下:
CREATE TABLE IF NOT EXISTS steps (
id int NOT NULL AUTO_INCREMENT,
rid int NOT NULL COMMENT 'route ID',
seq int NOT NULL COMMENT 'sequence',
longitude decimal(10,7) NOT NULL,
latitude decimal(10,7) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
我的问题是:如何让mysql搜索更快。所以,
-
由于mysql每次查询只使用一个索引,所以简单地为
latitude
和longitude
添加索引似乎效率不高?
我不能使用 MySQL 地理空间扩展,任何解决方案都必须是原生 MySQL。
由于应用程序要在视口内显示“路线”,因此有必要获取与给定视口相邻但在给定视口之外的点。
使用 geohash 等解决方案会有帮助吗?如果是这样,我该如何调整与给定视口相关的 geohash 的适当长度?
编辑
我无法使用 MySQL 的地理空间功能,因为该应用程序运行在 MySQL 5.7 上,我没有管理权限,即使我可以安排添加此扩展(如果 5.7 支持的话),不利于我们是因为我们不想给我们系统的其他部分引入任何不兼容性。
该应用程序是显示高度“集群”的“历史”路线,即随着时间的推移,可能会有很多靠近在一起的路线,在几个站点中,每个站点可能占据例如1k~2k平方公里。所以,除了我已经问过的问题之外,另一个问题可能是:如果某些路线大部分被其他路线覆盖,如何消除封闭在一起的路线。
【问题讨论】:
已添加空间索引,因为很难使用先前存在的索引进行索引(因此您必须限制自己的索引)。根据您的数据,您可能能够实现空间索引的简单手动版本:将您的点预分类到适当的区域,然后,如果您的边界框,例如位于法国,您可以忽略所有预先标记为不在法国的点。 “路线”是纬度/经度点的有序列表吗?判断两条路由是否“靠近”的度量标准是什么?两条“关闭”路线可以有不同数量的“点”吗? 5.7.1 InnoDB DATA_GEOMETRY (SPATIAL) 5.7.5 ADD SPATIAL INDEX(geom) 5.7.6 SPATIAL: ST_Distance_Sphere() 和 ST_MakeEnvelope() 也许你需要球形 SRS 而不是平面;我认为这要到 8.0 才到来。 您在全球哪个地区工作?如果区域足够小,合适的投影可以将其视为平坦的。 @RickJames 判断两条路线接近的标准是棘手的部分。目的是尽可能少地将点返回到前端,标准是如果两条线在谷歌地图上视觉上无法区分,那么它们是“靠近在一起”的。即它与地图的缩放和显示的分辨率有关... 【参考方案1】:您的大部分查询效率将来自 (longitude)
或 (latitude)
上的索引。但是compound index 上的(latitude, longitude)
会让事情变得更快一些。为什么? MySQL 可以直接从索引中检索第二个坐标值,它还必须检查您的边界,而无需在表中查找它。这样可以节省 MySQL 服务器的时间和 IO。
除非您的表格很大,或者您的边界框非常大,或者您有其他一些应用程序性能问题,否则这将可以正常工作。如果没有,您可能应该开始使用地理空间扩展。
还有什么帮助?
将纬度和经度的数据类型从 DECIMAL 更改为 FLOAT(单精度)。 GPS数据可以用单精度浮点数充分表示,比较运算速度稍快。 (如果您的数据比 FLOAT 允许的更精确,那么您肯定知道您的大地基准和您使用的投影。)
如果您的数据点主要分布在东西方向(例如美国),请使用(longitude, latitude)
上的索引,因为经度更具选择性。如果它们主要从北到南(例如日本)分布,则反转索引中列的顺序:纬度更具选择性。
使您的复合索引与您的查询匹配。如果您的查询看起来像这样
SELECT rid, seq, latitude, longitude
FROM steps
WHERE rid = ###constant##
AND latitude BETWEEN ###southboundary### AND ###northboundary###
AND longitude BETWEEN ###westboundar### AND ###eastboundary###
ORDER BY seq
covering index 的最佳选择是(rid, latitude, longitude, seq)
。
我认为您的要求是获取通过您的边界框的所有路线。你可以用这个查询来做。
SELECT rid, seq, latitude, longitude
FROM steps
WHERE rid IN (
SELECT rid
FROM steps
WHERE latitude BETWEEN ###southboundary### AND ###northboundary###
AND longitude BETWEEN ###westboundar### AND ###eastboundary###
)
ORDER BY rid, seq
子查询的良好覆盖索引将是(latitude, longitude, rid)
。
这将在多大程度上扩大规模?如果与表中的纬度/经度值的范围相比,您的边界框很小,它将很好地扩展:索引列上的 BETWEEN 过滤器是高度优化的范围扫描用例。如果不了解您的应用程序的更多信息,很难说出 100k 行或 10m 行。您应该阅读 Marcus Winand 的 https://use-the-index-luke.com,以了解有关索引魔法的更多信息。
【讨论】:
根据您的经验,假设我在 AWS 上使用“足够”的 ec 实例,您的解决方案将处理多少点并获得不错的性能?谢谢。 请在我的回答末尾查看我的编辑。 我发现优化器会在INDEX(lat, lng)
和INDEX(lng, lat)
之间进行选择。智利是一个很好的 N-S 例子!
在最后一个例子中,注意IN (SELECT ...)
的低效优化。该示例很容易转换为JOIN (SELECT rid...)
,但随后需要添加INDEX(rid)
。
@xrfang - 我在这里有一个比较:mysql.rjweb.org/doc.php/… - 一个简单的边界框花了大约 24 毫秒来搜索地球上 3.1M 中最近的 10 个城市(限于 50 英里)。相比之下,无索引需要 40 秒,空间需要 15 毫秒,Z 顺序需要 3 毫秒,分区需要 2 毫秒。够快吗? (计算机 CPU 自 2000 年以来变化不大,所以我的速度可能接近 AWS。)【参考方案2】:
有这些:
INDEX(lat, lng),
INDEX(lng, lat)
还有DROP
这些如果你有的话:INDEX(lat)
, INDEX(lng)
;他们会碍事。
优化器将使用这些 2 列索引中的任何一个,具体取决于哪个似乎在地球的 E-W 或 N-S 条带中的行数较少。
为什么不能使用SPATIAL
索引?它在 8.0 中可用。
这里是对 MySQL 中“查找最近”问题的彻底讨论。它包括对 lat/lng 所需精度的讨论。 http://mysql.rjweb.org/doc.php/find_nearest_in_mysql
这也适用于比简单边界框更快的算法。
我的博客使用“Z 顺序”索引,其工作原理类似于 gohash。我也研究过使用希尔伯特空间填充曲线。这有望具有与 Z 顺序相似的性能,但代码却截然不同。
分区和 Z 顺序解决方案命中的项目“不多”,而不是结果集中所需的项目。 2 列索引涉及更多项目,因为它们覆盖了整个纬度(或经度)条带。
你的SELECT
是什么样的?
至于geohash的可变长度,我对此表示怀疑。我查看了 Z-order 并得出结论,我需要超过 32 位且不超过 64 位。否则,算法会错误地将某些项目放置在边界框的内部或外部。
Spatial POINT
占用大量 25 个字节(相比之下,您的一对小数和我的博客中提到的一些较小的表示形式需要 12 个字节。)但我不明白为什么您不能使用 Point。对于车辆,我会选择FLOAT
(8 字节,1.7 m / 5.6 ft 分辨率)。对于人:DECIMAL(8,6)/(9,6)
(9 字节,16 厘米/1/2 英尺)。
【讨论】:
我解释了为什么我不能使用空间索引,并添加了一些关于我的应用程序的细节。另外,我在 find-nearest 上阅读了您的博客,我觉得我的要求可能比您的要求更容易(即计算成本更低)?【参考方案3】:在研究了 Rick 和 O.Jone 的回答后,我重新设计了表格。这里是:
CREATE TABLE `steps` (
`id` int NOT NULL AUTO_INCREMENT,
`rid` int NOT NULL COMMENT 'route ID',
`seq` int NOT NULL COMMENT 'step sequence',
`scale` tinyint unsigned NOT NULL COMMENT 'dp bitmask',
`longitude` decimal(10,7) NOT NULL,
`latitude` decimal(10,7) NOT NULL,
`grid` mediumint NOT NULL COMMENT 'grid number',
PRIMARY KEY (`id`),
UNIQUE KEY `ridseq` (`rid`,`seq`),
KEY `filter` (`scale`,`grid`,`longitude`,`latitude`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
“技巧”是 grid 和 scale,其中grid
是一个简单的分区方案,我只是将地球沿经度和高度分为 256 个部分。并使用uint16
记录给定点的网格#,其中第一个字节是经度,第二个字节是纬度。
比例是用作位掩码的一个字节值,它映射到地图视图的不同“缩放级别”。映射关系设计为:
DouglasPeucker Threshold Zoom Level
0.00005 16
0.0001 15,14
0.0004 13,12
0.0008 11,10
0.002 9,8
0.008 7
0.016 6
0.032 5
路线的起点和终点总是有scale=0xFF
,如果它出现在该缩放级别上,其他点的相关位将设置为1。
鉴于上述表格架构和三个输入参数:zoom-level
、south-west
和 northeast
,我可以使用以下查询:
SELECT rid,seq,longitude,latitude FROM steps WHERE
((grid>=... AND grid<=...) OR (...)) //grid filter
AND scale>=.. //zoom level filter
AND longitude BETWEEN .. AND ..
AND latitude BETWEEN .. AND .. //view port filter
ORDER BY rid,seq
我希望这个查询可以利用这个表上的索引。 EXPLAIN 显示:
possible_key:过滤器 键:NULL 行数:3783 过滤:0.07 额外:使用where;使用文件排序如果我删除ORDER BY
,Using filesort
将消失。我想知道这是否会减慢查询速度,即我应该在我的应用程序而不是 mysql 中查询数据并对其进行排序吗?
非常感谢您的评论。
【讨论】:
以上是关于如何在普通mysql中进行地理空间搜索的主要内容,如果未能解决你的问题,请参考以下文章