结合BTREE和地理/空间索引以加快查询速度?

我有一个带有500,000行的mysql myisam表.在此表中,我提供了不同类型的地点和纬度信息.经度坐标.根据用户的不同,我想在由纬度和经度定义的点的一定距离内选择某种地点.

我有一个空间索引和一个关于纬度,经度,类型的多列索引.如果特定区域内的行数不是太大,则itsel上的那些索引会很好地工作.

问题是在某些情况下,我需要从某个点(由纬度,经度坐标定义)开始使用很大的半径,因为所需类型的位置很少.但是问题是当我搜索某种类型时,说“ x”,因为我的半径很大,mysql说“ x”会搜索大约20,000行,说“ 200 km”.但是,在现实世界中,距某点200公里之内只有5个类型为“ x”的地方.

我读到某处BTREE和SPATIAL索引无法合并.但是,我想寻求一个解决方案,使我能够根据纬度,经度和类型的输入非常快速地选择这5个地方.

我尝试了以下2种方法:

方法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'

方法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秒.同样,使用第二种方法扫描(通过使用explain)的行数比第一种方法大.

使用方法1和方法2时,说明中的行数恰好是地理坐标在指定区域内的行数,并舍弃了该类型.我可以理解,对于方法1,类型不在索引中,但是对于方法2,我不希望对类型进行大表扫描,因为类型在索引中.

如果我可以使用纬度,经度和类型索引创建一个直接返回5点的索引,我希望此查询会更快.

由于我有许多此类查询,因此加快它们的速度非常重要.非常感谢您的帮助.

解决方法:

如果您只需要边界矩形搜索,则空间索引将产生最佳性能.

但这不是您所需要的.我相信,您需要在类型列和纬度/经度边界框范围内搜索某个单一值.无法创建具有空间成分并索引其他列的复合索引.

将FLOAT或DOUBLE数据用于纬度和经度

将FLOAT或DOUBLE数据类型用于纬度和经度列,以加快搜索速度. FLOAT对于GPS分辨率的地点查找器应用具有足够的精度. DOUBLE也可以正常工作.由于FLOAT数据项每个占用四个字节,而DOUBLE则占用八个字节,因此您会发现FLOAT的查找速度稍快.但这是一个微不足道的改进.

您可以将DECIMAL(8,4)或类似的数据类型用于经/纬度.但是FLOAT同样出色,而且速度明显更快.

如果lat / long值位于varchar()列中,则结果会出错或查询速度非常慢,因为范围扫描操作将无法正常进行.

使用复合BTREE索引

为此,我相信您最好的解决方案是在(类型,纬度,经度)上创建复合BTREE索引. MySQL将使用您指定的类型值和所需的下限纬度值随机访问此索引,然后对索引进行范围扫描,直到到达上限纬度.

索引范围扫描的说明

这是一个解释.可以随机访问BTREE索引以查找特定值,或者从任何起点按顺序访问以寻找下一个值.这是一个例子.假设您在名为data的列上有一个索引,并且其中包含具有值的行

 1
 2
 3
 5
 5
 6
 8
 9
11

如果您在4和9之间指定WHERE数据,MySQL将随机访问索引到第一个大于或等于4的值,然后顺序访问它,直到它获取到最后一个小于或等于9的值.这称为a范围扫描,看起来像这样.

 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
 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’并且纬度介于49.7和51.5之间的查询可以使用相同的范围扫描技巧.它寻找要捕获的第一行,然后扫描到最后一行.复合索引中列的顺序很重要,因为顺序排序是在列值的串联上进行的.

查找单个类型的经/纬度位置

您可以在问题中使用第二个查询或其他查询,以利用我建议的索引.

SELECT * 
  FROM destinations
 WHERE latitude  BETWEEN 49.7786783941 AND 51.5773216059 
   AND longitude BETWEEN 10.0927907742 AND 12.9312092258 
   AND type = 'x'

我不确定使用索引中包含的经度是否更好.值得尝试.

通过避免选择来提高性能*

专家提示:避免在此类查询中使用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米,而且地球的形状不是球形,可导致基于纬度的简单距离计算在同一水平上分解.

编辑我刚刚对我的邮政编码测试数据进行了实验,创建复合索引有很大帮助.

上一篇:The Geography of the Ice Age & 中国女排》


下一篇:python-当前在Google App Engine上存储,索引和查询地理空间数据的选项是什么?