我拥有一个包含2000万个地理点记录的庞大数据库,并且每天都在增长。
[id (int)] [group (int)] [latitude (double)] [longitude(double)]
[1] [1] [22.365598] [12.55678]
[2] [1] [22.365548] [12.55238]
[3] [2] [24.665348] [13.10238]
现在我希望在给定的boudingbox中的每个点。boudingbox的大小为南非,查询应该返回大约7.000个结果。但是需要30秒才能得到结果。
查询是:
SELECT distinct(group), id from `table`
where (latitude between -95.22 and 36.458 and longitude between -51.939 and 103.833);
索引是[纬度,经度](btree)。
我怎么能加快速度呢?
修改
我想要完成的事情
数据库包含大量多边形。比如说国家公园。 多边形中的每个节点都位于此表中。现在我想检查国家公园是否在给定位置的范围内。
Id是节点id,组是它所属的多边形,纬度和经度是节点的位置。
当我不使用distinct时,查询将在3秒内完成,但返回900.000结果。在其余代码中要处理的很多。
Sollution
正如戈登·林诺夫(Gordon Linoff)在他的回答中所说:这是一个非常大的表面。该查询用于一些详细的结果。对于这个大表面,我不应该使用所有多边形的所有节点,而是使用多边形的中心线。 当我需要小表面的详细结果时,此查询运行得足够快。
所以我认为我坚持这一点。
答案 0 :(得分:1)
首先,括号与distinct
无关。所以,只需将查询写为:
SELECT distinct `group`, id
from `table`
where latitude between -95.22 and 36.458 and
longitude between -51.939 and 103.833;
这种类型的查询 - 带有两个between
- 并不适合索引。您可以尝试latitude, longitude
或longitude, latitude
上的索引,并且可能会提供一些小的速度增量。
更好的方法是使用空间索引。 Here是开始了解它们的地方。
然而,即使是空间索引也不太可能有太大帮助。查询中的区域约占地球表面的1/6。如果您的观点是均匀分布的,则需要聚合超过300万条记录(对于select distinct
)。你可能没有太多运气来获得这个查询的真正好的表现。
答案 1 :(得分:0)
这不是你问题的直接答案,但是如果你已经使用MySQL 5.5或更高版本并且你可以选择更改数据模型,我建议你使用Point
数据类型并添加空间索引
http://dev.mysql.com/doc/refman/5.0/en/using-spatial-data.html
否则我会建议您省略不同的内容,因为它有时会成为性能瓶颈,而是添加group by
,我建议也将group
包含在索引中。
答案 2 :(得分:0)
如果添加包含整数字段的字段以及确切的teritory索引,该怎么办?防爆。 (latitude between -95.22 and 36.458 and longitude between -51.939 and 103.833);
= 1; (some other lat/long span)
= 2等。然后,您只需重新计算每条记录的值,并将值存储在新字段中。所有新的(更新的)记录都可以在插入(更新)触发器之前处理,以便为每个附加的(更新的记录)设置位置整数字段。所有SELECT查询都将使用此字段而不是lat / long双字段。
这将是一些数据冗余,但如果您拥有有限的地区列表,可能会对您有所帮助。您可以使用第二个表来存储地区列表及其ID。
答案 3 :(得分:0)
正如The Range Access Method for Multiple-Part Indexes所述:
只要比较运算符为
=
,<=>
或IS NULL
,优化程序就会尝试使用其他关键部分来确定间隔。如果运营商是>
,<
,>=
,<=
,!=
,<>
,BETWEEN
或{{3优化器使用它,但不再考虑关键部分。
换句话说,MySQL仅使用您的索引查找latitude
落在指定范围内的记录 - 然后从表中提取这些记录并扫描它们以在longitude
上执行过滤。
如果你考虑LIKE
的结构如何,那么MySQL这样做的原因是显而易见的:
Bd ________/ \_______ / \ Ad Cd __/ \__ __/ \__ / \ / \ Ab Bb Cb Db / \ / \ / \ / \ Aa Ac Ba Bc Ca Cc Da Dc
过滤范围的第一个关键部分(例如,上例中第一个字符为BETWEEN 'B' AND 'C'
,但在您的情况下为纬度标准)非常简单,因为树已经相对于第一个关键部分:
Bd ________/ \_______ / \ \ Cd \__ __/ \ / Bb Cb / \ / \ Ba Bc Ca Cc
但是,在第二个关键部分进行过滤时(例如,在此示例中第二个字符为BETWEEN 'b' AND 'c'
,但在您的情况下为经度标准),生成的已修剪树无法帮助,因为它不是相对于第二个关键部分排序。相比之下,如果第一个关键部分已经过滤精确匹配(而不是范围),则生成的修剪树将然后已经被第二个关键部分排序。
因此,B树对于定位多维范围无济于事。 B-tree是一种替代数据结构,更适合此类问题。 MySQL能够使用其R-tree:
创建R树索引创建一个spatial extensions的新列(例如POINT
),该列将保存您的坐标数据并spatial data type:
ALTER TABLE `table`
ADD coordinates POINT,
ADD SPATIAL INDEX (coordinates);
从现有数据中填充该列:
UPDATE `table` SET coordinates = Point(longitude, latitude);
您可能希望定义触发器和/或视图以协助进一步迁移。
执行搜索:
SELECT DISTINCT `group`, id
FROM `table`
WHERE MBRContains(
MultiPoint(Point(-51.939, -95.22), Point(103.833, 36.458)),
coordinates
)
这种方法特别好用的是,从MySQL 5.6.1开始,您可以index执行更精确的搜索:例如定义准确代表国界的多边形。
更新您的应用程序以使用此新列,例如:
SELECT X(coordinates) AS longitude, Y(coordinates) AS latitude FROM `table`
您可能希望定义触发器和/或视图以协助迁移。
删除旧列:
ALTER TABLE `table` DROP longitude, DROP latitude;
但是,您应该注意到MySQL的空间扩展使用欧几里德几何(显然,地球是球形的):这不应该影响上述操作,但要小心使用它来执行计算比如距离。