st_distance_sphere函数直接返回米制球面距离,要求point参数为经度在前纬度在后,需确保坐标系为wgs84且字段类型为point,否则结果失真。

ST_Distance_Sphere 函数能直接返回米制距离
ST_Distance_Sphere 是 MySQL 5.7.6+ 原生支持的地理空间函数,输入两个 POINT(经度在前、纬度在后),直接返回球面距离(单位:米)。它比手写 Haversine 公式更可靠,也比 ST_Distance(平面欧氏距离)更准确——后者在经纬度坐标系下完全不可用。
常见错误是把经纬度顺序搞反:POINT(longitude, latitude),不是 POINT(lat, lng)。一旦颠倒,计算结果会严重失真,且不报错。
- 确保字段类型是
POINT,不是VARCHAR或JSON;否则需用ST_PointFromText(CONCAT('POINT(', lng, ' ', lat, ')'))转换 - 如果表中存的是
lng和lat两个DOUBLE字段,必须先用ST_MakePoint(lng, lat)构造POINT - 目标点建议用
ST_PointFromText('POINT(121.47 31.23)')预编译,避免每次查询都拼字符串
WHERE 条件里不能只靠 ST_Distance_Sphere 过滤
直接写 WHERE ST_Distance_Sphere(point_col, @target) 会导致全表扫描——<code>ST_Distance_Sphere 无法利用空间索引加速距离过滤。
正确做法是先用 ST_Contains + ST_Buffer 或更高效地用 MBRContains 搭配空间索引做粗筛:
WHERE MBRContains(
ST_Buffer(ST_PointFromText('POINT(121.47 31.23)'), 0.05),
point_col
)
其中 0.05 是大致对应 5km 的经纬度偏移量(粗略值,仅用于快速缩小范围),再在外层用 ST_Distance_Sphere 精确计算并排序。
- 必须给
point_col字段建立空间索引:ALTER TABLE locations ADD SPATIAL INDEX idx_point (point_col); -
ST_Buffer在 MySQL 中对POINT生成的是方形 MBR,不是圆形,所以只是近似加速,不影响最终结果精度 - 如果表数据量小(ORDER BY ST_Distance_Sphere(...) LIMIT 10 也勉强可接受,但别在大表上这么干
ORDER BY + LIMIT 组合必须放在子查询或 CTE 里才安全
MySQL 8.0+ 支持 CTE,但老版本容易因优化器误判导致 ORDER BY ST_Distance_Sphere 不生效,尤其配合 LIMIT 时可能返回非最近的几条。
稳妥写法是把距离计算和排序包进派生表:
SELECT id, name, dist
FROM (
SELECT id, name,
ST_Distance_Sphere(point_col, ST_PointFromText('POINT(121.47 31.23)')) AS dist
FROM locations
WHERE MBRContains(ST_Buffer(ST_PointFromText('POINT(121.47 31.23)'), 0.05), point_col)
ORDER BY dist
LIMIT 20
) t
ORDER BY dist;
- 外层再套一层
ORDER BY是为了防止内层排序被优化器忽略(某些 MySQL 5.7 版本存在该问题) - 别在
SELECT里重复调用ST_Distance_Sphere,比如同时用于WHERE和SELECT,会算两遍,拖慢速度 - 如果只需要最近 1 个,
LIMIT 1比MIN()+ 子查询更快,因为后者仍需扫描全量候选
注意时区无关但坐标系必须是 WGS84
ST_Distance_Sphere 内部按 WGS84 椭球模型计算,前提是你的经纬度数据确实是 WGS84 坐标系(国内高德、百度等 SDK 默认返回的不是 WGS84,而是 GCJ-02 或 BD-09)。
如果原始数据来自百度地图 API,直接入库会导致距离偏差达几百米——必须先转成 WGS84 才能用 ST_Distance_Sphere。
- MySQL 不提供坐标系转换函数,转换必须在应用层或 ETL 阶段完成
- 检查数据是否为 WGS84 的最简单方式:拿已知位置(如天安门广场)的公开 WGS84 坐标对比入库值,误差 > 0.005° 就大概率不是
- 别指望
ST_Transform(MySQL 不支持该函数)或加个 SRID 参数解决——MySQL 目前所有空间函数都隐式假设 WGS84,不校验也不转换
真正麻烦的从来不是函数怎么写,而是你手里的经纬度到底是什么坐标系。没确认这点之前,所有距离结果都只是参考值。











