在 LBS(基于位置的服务)应用中,常见的需求包括:
✅ 查询附近的人(社交应用、打车、配送等)
✅ 查询某个点是否在指定范围内(地理围栏,例如外卖配送范围、进出考勤范围)
✅ 查找某个区域内的所有点(如商家、用户)
MySQL 5.7+ 提供了GIS(地理信息系统)支持,包括空间数据类型(Point、Geometry)和空间索引(SPATIAL INDEX),可以高效存储和查询地理数据。本文将介绍 MySQL 如何实现“附近的人”和“地理围栏”查询,并提供高效的索引优化方案。
1. 创建用户位置表(存储经纬度)
首先,我们创建一个 user_location 表来存储用户的地理位置信息。
CREATE TABLE user_location (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL, -- 用户 ID
location POINT NOT NULL, -- 经纬度坐标
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
SPATIAL INDEX idx_location (location) -- 空间索引
);
✅ 优化点:
- POINT NOT NULL 存储地理坐标(格式:POINT(longitude, latitude),经度在前,纬度在后!)。
- SPATIAL INDEX 空间索引加速地理查询。
- updated_at 记录最后更新时间,方便清理过期位置数据。
2. 插入用户位置数据
使用 ST_GeomFromText() 存储 POINT(经度 纬度) 格式的数据。
INSERT INTO user_location (user_id, location)
VALUES (1, ST_GeomFromText('POINT(116.403963 39.915119)')); -- 北京天安门
批量插入示例:
INSERT INTO user_location (user_id, location) VALUES
(2, ST_GeomFromText('POINT(116.404 39.915)')), -- 附近用户
(3, ST_GeomFromText('POINT(116.500 39.920)')), -- 稍远的用户
(4, ST_GeomFromText('POINT(116.600 40.000)')); -- 更远的用户
3. 查询附近的人(ST_Distance_Sphere)
3.1 查找 5km 内的用户
假设当前用户的位置是 北京天安门(116.403963, 39.915119),我们想要查找5km 内的用户。
SELECT user_id,
ST_Distance_Sphere(location, ST_GeomFromText('POINT(116.403963 39.915119)')) AS distance
FROM user_location
WHERE ST_Distance_Sphere(location, ST_GeomFromText('POINT(116.403963 39.915119)')) <= 5000
ORDER BY distance ASC;
✅ 优化点:
- ST_Distance_Sphere() 计算球面距离(单位:米),适用于小范围查询(如城市内)。
- ORDER BY distance 让最近的人排在前面。
⚠️ 注意:ST_Distance_Sphere() 不走索引,适用于小规模数据(百万级以内)!
3.2 用索引优化查询(预过滤 + 精确计算)
如果用户量很大(上千万),查询 ST_Distance_Sphere() 可能会遍历整个表,影响性能。
✅ 优化方法:
- 使用 MBRContains() 先用索引筛选大致范围(如 5km 内的正方形)。
- 再用 ST_Distance_Sphere() 计算精确距离。
SELECT user_id,
ST_Distance_Sphere(location, ST_GeomFromText('POINT(116.403963 39.915119)')) AS distance
FROM user_location
WHERE MBRContains(
ST_Buffer(ST_GeomFromText('POINT(116.403963 39.915119)'), 0.05),
location
)
ORDER BY distance ASC
LIMIT 50; -- 取前 50 个最近的人
✅ 优化点:
- ST_Buffer(…, 0.05) 生成一个大致 5km 的矩形范围,先用 MBRContains() 走索引筛选出可能的用户。
- 再用 ST_Distance_Sphere() 计算精确距离,提高查询效率。
- LIMIT 50 限制结果,避免查询过多数据。
4. 地理围栏查询:判断用户是否在某个区域内
4.1 商家配送范围(Polygon + ST_Contains)
假设我们有一个商家,配送范围是北京市区的多边形,我们想要判断用户是否在配送范围内。
CREATE TABLE delivery_area (
id INT AUTO_INCREMENT PRIMARY KEY,
store_id INT NOT NULL, -- 商家 ID
area POLYGON NOT NULL, -- 多边形配送范围
SPATIAL INDEX idx_area (area) -- 空间索引
);
✅ POLYGON 存储配送区域,SPATIAL INDEX 加速查询。
插入一个配送区域(北京市区):
INSERT INTO delivery_area (store_id, area)
VALUES (1, ST_GeomFromText('POLYGON((116.20 39.80, 116.60 39.80, 116.60 40.10, 116.20 40.10, 116.20 39.80))'));
查询用户是否在配送范围内:
SELECT store_id
FROM delivery_area
WHERE ST_Contains(area, ST_GeomFromText('POINT(116.403963 39.915119)'));
✅ ST_Contains() 判断 POINT 是否在 POLYGON 内,适用于商家配送范围、考勤打卡范围等场景。
5. 地理围栏查询:查找某个区域内的所有用户
5.1 查询北京市区内的所有用户
SELECT user_id
FROM user_location
WHERE ST_Within(location,
ST_GeomFromText('POLYGON((116.20 39.80, 116.60 39.80, 116.60 40.10, 116.20 40.10, 116.20 39.80))'));
✅ ST_Within() 检查 location 是否在 POLYGON 内,适用于查找特定区域的用户。
6. 总结:不同地理查询的实现方式
查询需求 | SQL 方案 | 适用场景 |
---|---|---|
查找附近的人(5km 内 | ST_Distance_Sphere() | 适合小规模数据(百万级) |
优化查找附近的人(亿级数据) | MBRContains() + ST_Distance_Sphere() | 大规模数据,走索引先筛选 |
判断某个点是否在区域内 | ST_Contains() | 商家配送范围、考勤围栏 |
查询某个区域内的所有用户 | ST_Within() | 城市、商圈查询 |
🚀 推荐方案:
- 小数据量(百万级以内):直接用 ST_Distance_Sphere()。
- 大数据量(上千万级):先用 MBRContains() 走索引,再用 ST_Distance_Sphere() 精确计算。
- 固定范围查询(商家配送、考勤等):用 ST_Contains() 或 ST_Within() 提高查询效率。
7. 结论
MySQL 的 GIS 数据类型(POINT、POLYGON)和空间索引(SPATIAL INDEX),为LBS 场景提供了高效的查询能力。结合索引优化,可以加速“附近的人”、地理围栏查询,提高地理计算性能。🚀