Mysql - select 记录用户在其个人距离范围内的 lat/lng 位置

Mysql - select records for lat/lng position that users have in their individual distance ranges

我需要了解以下场景 (Mysql) 的数据库查询的最佳实践(最佳性能):

http://sqlfiddle.com/#!9/72191ca/1

我有一个 "starting-item"(点 "Key",蓝点 lat:47.471630,lng: 8.297835),位置 lat/lon。在用户 table 中,有用户(A、B、C 等)及其 lat/lon 位置和以公里为单位的个人范围。

我需要查询用户 table 以找到在其预定义 ranges/distance 中具有密钥的 ID。

应该优化查询 - 大约 40'000 个用户与 "Key" lat/lon 位置进行比较。

这是我当前使用的查询。性能非常好,但是是否有另一种可以使用索引的解决方案?

DROP TABLE IF EXISTS users;

CREATE TABLE `users` (
  `user_id` char(1) NOT NULL,
  `lat` decimal(8,5) NOT NULL DEFAULT '0.00000',
  `lng` decimal(8,5) DEFAULT '0.00000',
  `user_range_km` decimal(10,1) NOT NULL DEFAULT '1.0',
  PRIMARY KEY (`user_id`),
  KEY `lat` (`lat`,`lng`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;


INSERT INTO `users` (`user_id`, `lat`, `lng`, `user_range_km`) VALUES
('A', '47.46911', '8.29560', '0.4'),
('B', '47.48169', '8.30264', '0.4'),
('C', '47.49261', '8.31598', '2.9');

SELECT h.*, ( 6371 * acos( cos( radians(47.471630) ) * cos(  radians( h.lat ) ) * cos( radians( h.lng ) - radians(8.297835) ) + sin( radians(47.471630) ) * sin( radians( h.lat ) ) ) ) AS distance 
FROM users h 
HAVING distance <= h.user_range_km;

+---------+----------+---------+---------------+------------------+
| user_id | lat      | lng     | user_range_km | distance         |
+---------+----------+---------+---------------+------------------+
| A       | 47.46911 | 8.29560 |           0.4 | 0.32671077638732 |
| C       | 47.49261 | 8.31598 |           2.9 |  2.7021411331883 |
+---------+----------+---------+---------------+------------------+

在我的示例中,A 和 C 在其定义的距离内具有 Key,因此我需要从查询中获取 A 和 C。见 SQL Fiddle

有 5 种方法可以完成该任务。您的代码是其中之一,最慢的一个。这是我对它们的讨论:http://mysql.rjweb.org/doc.php/find_nearest_in_mysql

最简单的下一步是使用 "bounding box" 技术。它涉及向 WHERE 添加 2 个子句和两个 INDEXes.

你的 "multiple users" 左右一个 "key" 只是在颠倒角色。传统问题是关于 "multiple items (businesses, trucks, etc)" 围绕 "user"。在 "key".

周围构建边界框

只有 40K 用户,边界框技术可能就足够了。

好的,你又多了一条皱纹。不过,BB 应该会给你一个很好的第一个过滤器。在您的示例中,使用

MAX(user_range_km) -- which is 2.9

作为 BB 的半径。 (或评论中提到的 "bounding square" 宽度的一半。)

然后,不是简单地针对 2.9 测试每个 'distance',而是针对 user_range_km.

进行测试