我有一个包含500,000行的mysql myisam表。在这张表中,我有不同类型的地方和纬度和信息的信息。经度坐标。根据用户的不同,我想在距离纬度和经度定义的一定距离内选择一定类型的地点。
我在纬度,经度,类型上有空间索引和多列索引。如果某个区域内的行数不是太大,那么itsel上的那些索引效果很好。
问题在于,在某些情况下,我需要从某个点(由纬度,经度坐标定义)使用非常大的半径,因为所需类型的地方非常少。然而问题是当我搜索某种类型时,说“x”mysql搜索大约20,000行,因为我的半径很大,比如说“200 km”。然而在现实世界中,距离某一点200公里内只有5个类型为“x”的地方。
我在某地读过BTREE和SPATIAL索引无法合并。但是我想要找到一个解决方案,我可以根据纬度,经度和类型的输入快速选择这5个地方。
我尝试了以下两种方法:
方法1 - 空间索引:
SELECT * FROM destinations
WHERE MBRWithin(lat_lng_point, GeomFromText('Polygon((49.8413216059 12.8478000082, 48.0426783941 12.8478000082, 48.0426783941 15.5861999918, 49.8413216059 15.5861999918, 49.8413216059 12.8478000082))'))
AND destinations.type = 'x'
APPROACH 2 - 纬度,经度,类型的多列索引:
SELECT * FROM destinations FORCE INDEX (lat_long_type_main)
WHERE latitude > 49.7786783941 AND latitude < 51.5773216059
AND longitude > 10.0927907742 AND longitude < 12.9312092258
AND type = 'x'
方法1仍然比方法2快得多,因为它们分别需要2到5秒。此外,第二种方法扫描的行数(通过使用说明)比第一种方法更大。
使用方法1和方法2,解释中的行数正是地理坐标指定区域内的行数,从而丢弃了类型。我可以理解,对于方法1,类型不在索引中,但不适用于方法2我不希望类型的大型表扫描在索引中。
如果我可以使用纬度,经度和类型的索引创建一个直接返回5个点的索引,我希望这个查询要快得多。
由于我有很多这样的问题,所以加快它们是非常重要的。我非常感谢你的帮助。
答案 0 :(得分:3)
如果您只需要一个边界矩形搜索,空间索引将产生最佳性能。
但那不是你所需要的。我相信,您需要在type
列和纬度/长边界框范围内搜索某个值。无法创建具有空间组件的复合索引,也可以索引其他列。
使用FLOAT
或DOUBLE
列的latitude
或longitude
数据类型进行最快速搜索。 FLOAT
具有足够的精度,可用于GPS分辨率的寻星器应用程序。 DOUBLE
也会运作良好。由于FLOAT
个数据项每个占用四个字节而DOUBLE
占用八个字节,因此您会发现查找FLOAT
的速度稍快一些。但这是一个微不足道的改进。
您可以使用DECIMAL(8,4)
或某些类似的数据类型作为lat / long。但是FLOAT
同样好,而且明显更快。
如果您的纬度/经度值位于varchar()
列,您将会在结果中出错或查询速度非常慢,因为范围扫描操作无法正常工作。
为此,我相信您最好的解决方案是在(type, latitude, longitude)
上创建复合BTREE索引。 MySQL将使用您指定的type
值和您想要的下限latitude
随机访问此索引,然后对索引进行范围扫描,直到它到达上限latitude
以下是对此的解释。可以随机访问BTREE索引以查找特定值,或者从寻找下一个值的任何起始点按顺序访问BTREE索引。这是一个例子。假设您在名为data
的列上有索引,并且它包含值为
1
2
3
5
5
6
8
9
11
如果指定WHERE data BETWEEN 4 AND 9
,MySQL将随机访问索引到大于或等于4的第一个值,然后依次访问它,直到它达到小于或等于9的最后一个值。这是称为范围扫描,看起来像这样。
1
2
3
5 <-- random access to here.
5 <-- scan to here
6 <-- ... and here
8 <-- ... and here
9 <-- ... and here
11 <-- stop scanning right before this row.
此扫描非常快。
现在让我们在type
和latitude
上考虑复合索引,如您的问题所示。该索引可能包含这些值。
type latitude
a 49.5
a 49.8
a 49.9
a 52.0
b 58.3
x 49.5
x 49.8 <-- random access to here
x 51.2 <-- ... scan to here
x 51.8 <-- stop scanning right before this row
y 49.0
y 49.5
看起来像WHERE type='x' AND latitude BETWEEN 49.7 AND 51.5
的查询可以使用相同的范围扫描技巧。它查找要捕获的第一行,然后扫描到最后一行。复合索引中列的顺序很重要,因为顺序排序是列值的串联。
您可以在问题中使用第二个查询,或使用其中的某个变体来利用我建议的索引。
SELECT *
FROM destinations
WHERE latitude BETWEEN 49.7786783941 AND 51.5773216059
AND longitude BETWEEN 10.0927907742 AND 12.9312092258
AND type = 'x'
我不确定您是否会更好地使用索引中包含的longitude
。这值得一个实验。
专业提示:在此类查询中避免使用SELECT *
。如果从查询中枚举所需的字段,则可以创建可直接满足查询的覆盖索引。那将是非常快的。例如,如果您的查询是
SELECT airport_code, name, latitude, longitude
FROM destinations
WHERE latitude BETWEEN 49.7786783941 AND 51.5773216059
AND longitude BETWEEN 10.0927907742 AND 12.9312092258
AND type = 'x'
然后,您可以通过此复合BTREE索引的范围扫描直接满足您的查询。
(type, latitude, longitude, airport_code, name)
注意:您不必为创建BTREE索引做任何特殊操作。这是默认值。
专业提示:您可能通过提供精确的坐标来欺骗自己,例如51.5773216059。这是一个约11微米的表观精度。 GPS只有大约5米的距离,并且地球的非球形形状导致简单的基于纬度的距离计算在同一水平上发生故障。
编辑我刚刚使用我的邮政编码测试数据进行了实验,创建复合索引有很大帮助。