MariaDB - 地理编码查询非常慢

Posted

技术标签:

【中文标题】MariaDB - 地理编码查询非常慢【英文标题】:MariaDB - Geocode query very slow 【发布时间】:2016-07-27 10:11:19 【问题描述】:

我正在使用 MariaDB 10.1.16,我有一个包含 170 万个英国邮政编码的表格,将用于位置自动完成、地理编码和反向地理编码。

下面是表结构:

+-------------+--------------+------+-----+---------+-------+
| Field       | Type         | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+-------+
| postcode    | varchar(8)   | NO   | PRI | NULL    |       |
| district    | varchar(4)   | YES  |     | NULL    |       |
| postal_town | varchar(35)  | YES  |     | NULL    |       |
| county      | varchar(37)  | YES  |     | NULL    |       |
| country     | varchar(16)  | YES  |     | NULL    |       |
| easting     | int(11)      | YES  |     | NULL    |       |
| northing    | int(11)      | YES  |     | NULL    |       |
| latitude    | decimal(7,5) | YES  |     | NULL    |       |
| longitude   | decimal(7,5) | YES  |     | NULL    |       |
| type        | varchar(14)  | YES  |     | NULL    |       |
| id          | varchar(32)  | YES  |     | NULL    |       |
+-------------+--------------+------+-----+---------+-------+

在表中查询单个邮政编码会立即返回结果:

MariaDB [dev]> SELECT * FROM uk_postcodes WHERE postcode = "CH5 3NS";
+----------+----------+-------------+--------+---------+---------+----------+----------+-----------+------------+----------------------------------+
| postcode | district | postal_town | county | country | easting | northing | latitude | longitude | type       | id                               |
+----------+----------+-------------+--------+---------+---------+----------+----------+-----------+------------+----------------------------------+
| CH5 3NS  | CH5      | Hawarden    | Clwyd  | Wales   |  331718 |   365725 | 53.18422 |  -3.02325 | Geographic | f99a64139bfb8cf8091ca870808b355b |
+----------+----------+-------------+--------+---------+---------+----------+----------+-----------+------------+----------------------------------+
1 row in set (0.00 sec)

但是我现在想使用我当前的位置来查找最近的邮政编码。

我的大概位置是:53.1852582, -3.0198408999999997

我的查询:

MariaDB [dev]> SELECT postcode, ( 3959 * acos( cos( radians( 53.1852582 ) ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians(-3.0198408999999997) ) + sin( radians(53.1852582) ) * sin( radians( latitude ) ) ) ) AS distance
    -> FROM uk_postcodes
    -> ORDER BY distance ASC
    -> LIMIT 1;
+----------+---------------------+
| postcode | distance            |
+----------+---------------------+
| CH5 3PF  | 0.13510896180231324 |
+----------+---------------------+
1 row in set (3.10 sec)

虽然结果是正确的,但它花费的时间太长了。但为什么呢?

【问题讨论】:

由于您使用 ORDER BY,因此您必须在返回数据之前对所有条目执行计算并对结果集进行排序。这需要相当长的时间...如果您对查询进行 EXPLAIN 说明,您可能会明白为什么... 您将需要对数据使用某种空间索引,否则您将无法从数据库中获得任何合理的性能。 您真的需要查看所有条目,还是将数据减少到预定义的半径就足够了? @Olli 现在是的,我可以将其限制在特定区域,从而将总可能性从 1.7M 减少到 23K。但本质上这并不能解决问题,所以我仍然真的需要理解为什么需要这么长时间。 Whats the fastest way to lookup big tables for points within radius mysql (latitude longitude)的可能重复 【参考方案1】:

这个答案不是关于它为什么这么慢的原因(它在订购之前计算数据库中每一行的值),但可能是一个快速的黑客来减少要检查的行数:

当您将纬度/经度值限制为附近的值时,您可以限制必须计算距离的数据范围。 (来自these slides 的公式(第 12 页)这在您按英里计算时有效......否则您必须使用正确的公里值调整“69”。

1° of latitude ~= 69 miles 
1° of longitude ~= cos(latitude)*69

然后你像这样修改你的查询:

SELECT 
  postcode, 
  ( 3959 * acos( cos( radians( 53.1852582 ) ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians(-3.0198408999999997) ) + sin( radians(53.1852582) ) * sin( radians( latitude ) ) ) ) AS distance
FROM 
  uk_postcodes
WHERE 
  longitude BETWEEN <longitude1> AND <longitude2> 
AND 
  latitude BETWEEN <latitude1> AND <latitude2>
ORDER BY 
  distance ASC
LIMIT 1;

使用前面提到的计算出的经度/纬度。

这应该可以提高整体速度,而无需更改数据库结构。

【讨论】:

【参考方案2】:

我已经设法解决了性能问题!

表结构

+-------------+--------------+------+-----+---------+-------+
| Field       | Type         | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+-------+
| postcode    | varchar(8)   | NO   | PRI | NULL    |       |
| district    | varchar(4)   | YES  |     | NULL    |       |
| postal_town | varchar(35)  | YES  | MUL | NULL    |       |
| county      | varchar(37)  | YES  |     | NULL    |       |
| country     | varchar(16)  | YES  |     | NULL    |       |
| easting     | int(11)      | YES  |     | NULL    |       |
| northing    | int(11)      | YES  |     | NULL    |       |
| latitude    | decimal(7,5) | YES  |     | NULL    |       |
| longitude   | decimal(7,5) | YES  |     | NULL    |       |
| type        | varchar(14)  | YES  |     | NULL    |       |
| id          | varchar(32)  | YES  |     | NULL    |       |
| latlng      | geometry     | NO   | MUL | NULL    |       |
+-------------+--------------+------+-----+---------+-------+

空间索引

latlng 字段具有空间索引,并已按如下方式填充:

UPDATE uk_postcodes SET latlng = GeomFromText(CONCAT('POINT(',latitude,' ', longitude, ')'));

SQL 查询

SELECT postcode, ( 3959 * acos( cos( radians( 53.18526 ) ) * cos( radians( X(latlng) ) ) * cos( radians( Y(latlng) ) - radians(-3.01984) ) + sin( radians(53.18526) ) * sin( radians( X(latlng) ) ) ) ) AS distance
FROM uk_postcodes
WHERE MBRContains
(LineString
  (
    Point (53.18526 + 10 / (111.1 / COS(RADIANS(53.18526))), -3.01984 + 10 / 111.1),
    Point (53.18526 - 10 / (111.1 / COS(RADIANS(53.18526))), -3.01984 - 10 / 111.1)
  ),
  latlng
)
ORDER BY distance
LIMIT 1;

结果

+----------+---------------------+
| postcode | distance            |
+----------+---------------------+
| CH5 3PF  | 0.13513453795504218 |
+----------+---------------------+
1 row in set (0.00 sec)

【讨论】:

以上是关于MariaDB - 地理编码查询非常慢的主要内容,如果未能解决你的问题,请参考以下文章

Android高德通过经纬度获取地理位置

地理编码服务[关闭]

这里API:对自由文本查询进行地理编码可返回正确的结果,但没有合格的查询

使用 Google Maps Javascript API V3 反向地理编码检索邮政编码

如何实现地理位置与经纬度坐标的批量转换

地理编码 - 获取位置