MySQL 5.7+ 应使用 ST_Distance_Sphere(WGS84椭球模型,单位米)计算真实地球距离,而非 ST_Distance(平面欧氏距离,单位度);字段需为 POINT SRID 4326,目标点用 ST_PointFromText('POINT(经度 纬度)', 4326) 构造,经度在前、纬度在后,并建 SPATIAL 索引;但空间索引不加速排序,需配合 Redis GEO 粗筛(注意 GCJ-02 偏移纠偏)再 MySQL 精算。

MySQL 5.7+ 用 ST_Distance_Sphere 算真实地球距离,别用 ST_Distance
MySQL 的空间函数容易误选——ST_Distance 返回的是平面欧氏距离(单位是度),对经纬度直接算结果毫无地理意义;而 ST_Distance_Sphere 才按 WGS84 椭球模型计算米级距离,才是“附近人”真正能用的。
实操要点:
- 字段必须是
POINT类型,且用SRID 4326(WGS84 坐标系),建表时显式声明:location POINT SRID 4326
- 查询时务必用
ST_PointFromText('POINT(经度 纬度)', 4326)构造目标点,顺序是「经度在前、纬度在后」,反了会查到南美洲去 - 加
SPATIAL索引才有效:CREATE SPATIAL INDEX idx_location ON users(location);
,但注意:MySQL 的空间索引只加速范围过滤(如ST_Within),不加速ST_Distance_Sphere排序,所以得先圈个粗略范围再算精距
Redis GEO 指令适合高并发轻量级场景,但精度只有 5 位小数
Redis 的 GEOADD/GEORADIUS 响应快、天然支持限流和缓存穿透防护,适合“展示最近 20 个头像”这类低精度需求;但它内部用 GeoHash 编码,纬度/经度被截断到 52 位,等效精度约 1 米——听起来准,实际在高密度城区(比如上海静安寺周边)容易把 300 米外的人错拉进来,或漏掉紧邻楼下的用户。
使用注意:
立即学习“Java免费学习笔记(深入)”;
-
GEORADIUS默认单位是m(米),别漏写:GEORADIUS users:geo 121.47 31.23 500 m WITHDIST
- 返回结果无序,要按距离排得加
ASC,且 Redis 不支持分页跳过前 N 条,想“加载更多”得自己记录上一页最后的距离值 + 成员名做游标 - 不能存非经纬度信息,用户昵称、头像 URL 得另建哈希表关联,键用用户 ID 对齐,否则数据不同步
混合方案:Redis 过滤粗粒度 + MySQL 算精距排序
单用谁都扛不住——Redis 距离不准,MySQL 排序太慢。折中做法是:先用 Redis 快速捞出 5 公里内的所有用户 ID(可能 500 个),再用这些 ID 去 MySQL 查真实坐标,调 ST_Distance_Sphere 算精确距离并 LIMIT 20 排序。
关键控制点:
- Redis 半径要放大(比如设 6km),避免因 GeoHash 边界效应漏人;MySQL 侧再用精算筛回 5km 内
- MySQL 查询用
WHERE id IN (…),ID 列必须有普通 B+Tree 索引,否则全表扫比不用 Redis 还慢 - 如果 Redis 返回空,说明真没人,别 fallback 到全量 MySQL 扫描——那会拖垮数据库
别忽略坐标系偏移:GCJ-02 国测局加密让所有原始 GPS 坐标失效
国内安卓手机、高德/腾讯地图 SDK 返回的经纬度默认是 GCJ-02 偏移坐标,不是 WGS84。如果你直接把手机上报的 116.39,39.92 存进 MySQL 的 SRID 4326 字段,算出来的距离会系统性偏差 100–700 米,北京五环内误差常超 300 米。
处理方式取决于数据来源:
- 若前端用高德地图 SDK,调
AMap.convertFrom主动转成 WGS84 再上传 - 若已存了一堆 GCJ-02 坐标,得用开源库(如
eviltransform)批量纠偏,别手写转换公式——网上流传的 JS 版本多数有精度缺陷 - 测试时拿两个已知真实距离的地址(比如公司和楼下咖啡馆),实测偏差值,比看文档更可靠










