结合 BTREE 和地理/空间索引来加速我的查询?

Bas*_*nWW 4 mysql indexing performance geospatial latitude-longitude

我有一个包含 500,000 行的 mysql myisam 表。在这张表中,我有不同类型的地点和经纬度坐标的信息。根据用户的不同,我想在距离由纬度和经度定义的点一定距离内选择某种类型的地方。

我有一个空间索引和一个关于纬度、经度、类型的多列索引。如果某个区域内的行数不是太大,那么这些索引就可以很好地工作。

问题是在某些情况下,我需要从某个点(由纬度、经度坐标定义)使用非常大的半径,因为所需类型的地方很少。然而问题是,当我搜索某种类型时,比如“x”,mysql 会搜索大约 20,000 行,因为我的半径很大,比如“200 公里”。然而,在现实世界中,距离某个点 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'
Run Code Online (Sandbox Code Playgroud)

方法 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'
Run Code Online (Sandbox Code Playgroud)

方法 1 仍然比方法 2 快得多,因为它们分别需要 2 到 5 秒。此外,与第一种方法相比,第二种方法扫描的行数(通过使用解释)更大。

对于方法 1 和方法 2,解释中的行数正好是地理坐标指定区域内的行数,丢弃类型。我可以理解,对于方法 1,类型不在索引中,但对于方法 2,我不希望对类型进行大表扫描,因为类型在索引中。

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

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

O. *_*nes 5

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

但这不是你所需要的。我相信,您需要在type列中搜索某个单个值,以及经纬度边界框范围。不可能创建一个复合索引,该索引具有空间组件并索引其他一些列。

使用 FLOAT 或 DOUBLE 数据获取纬度和经度

对和列使用FLOATorDOUBLE数据类型以加快搜索速度。对 GPS 分辨率的位置查找器应用程序具有足够的精度。也会很好用。因为数据项每个占用四个字节,占用八个字节,所以您会发现查找速度稍快。但这是一个微不足道的改进。 latitudelongitudeFLOATDOUBLEFLOATDOUBLEFLOAT

您可以使用DECIMAL(8,4)lat/long 或一些类似的数据类型。但FLOAT同样好,而且明显更快。

如果您的纬度/经度值在varchar()列中,您将在结果中出错或查询速度非常慢,因为范围扫描操作将无法正常工作。

使用复合 BTREE 索引

为此,我相信您最好的解决方案是在(type, latitude, longitude). MySQL 将使用type您指定的值和所需的下限值随机访问此索引latitude,然后将范围扫描索引直到它到达上限latitude

索引范围扫描说明

这是对此的解释。BTREE 索引可以随机访问以查找特定值,也可以从任何起点按顺序访问以查找下一个值。这是一个例子。假设您在名为 的列上有一个索引data,它包含带有值的行

 1
 2
 3
 5
 5
 6
 8
 9
11
Run Code Online (Sandbox Code Playgroud)

如果指定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.
Run Code Online (Sandbox Code Playgroud)

这种扫描速度非常快。

复合索引范围扫描说明

现在让我们考虑一个复合索引,就像你的问题一样,在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
Run Code Online (Sandbox Code Playgroud)

看起来像的查询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'
Run Code Online (Sandbox Code Playgroud)

我不确定你是否更好地longitude包含在索引中。这值得一试。

通过避免 SELECT * 提高性能

专业提示:避免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'
Run Code Online (Sandbox Code Playgroud)

然后您的查询可以直接通过对这个复合 BTREE 索引进行范围扫描来满足。

(type, latitude, longitude, airport_code, name)
Run Code Online (Sandbox Code Playgroud)

注意:您无需执行任何特殊操作即可创建 BTREE 索引。这是默认设置。

不要夸大你的纬度/经度精度

专业提示:您可能会通过提供精确的坐标(例如 51.5773216059)来欺骗自己。这是大约 11 微米的表观精度。GPS 仅适用于 5 米左右,并且地球的非球形形状导致简单的基于纬度经度的距离计算在同一水平上崩溃。

编辑我刚刚用我的邮政编码测试数据运行了一个实验,创建复合索引有很大帮助。

  • 我注意到空间索引比在纬度、经度(或仅纬度或经度)和类型上使用 BTREE 索引要快得多。我不知道究竟是为什么。因此,我真的想以某种方式将空间索引与 BTREE 索引结合起来,或者可能在 2 个点和一个类型上有一个空间索引?不知道这是否可能.. (2认同)