MySQL 5.7.6+ 应优先使用 ST_Distance_Sphere 函数计算球面距离,需确保参数为 SRID 4326 的 POINT 类型且经度在前、纬度在后,配合 SPATIAL 索引提升性能。

MySQL 5.7+ 直接用 ST_Distance_Sphere 最省事
如果你的 MySQL 版本 ≥ 5.7.6,且坐标字段是 POINT 类型(推荐用 SRID 4326),ST_Distance_Sphere 是唯一该优先考虑的函数——它直接返回米制单位的实际球面距离,精度高、写法简洁、无需手动套公式。
常见错误是传入普通数值而非 POINT:
SELECT ST_Distance_Sphere( POINT(-73.9857, 40.7484), -- 经度在前,纬度在后 POINT(-0.1246, 51.5007) );
注意:POINT(longitude, latitude),顺序反了结果就完全错;如果数据存的是 lat/lng 两个分离的 DOUBLE 字段,得先用 POINT(lng, lat) 构造。
- 必须确保两个
POINT的SRID一致(推荐显式设为4326) - 若表中已有
lat和lng字段,查询时可写成ST_Distance_Sphere(POINT(t.lng, t.lat), POINT(-73.9857, 40.7484)) - 性能上,对大表务必给
POINT字段加SPATIAL索引,否则全表扫描极慢
老版本 MySQL(
低于 5.7 的 MySQL 没有地理函数支持,只能靠 POW、SIN、COS 手算。公式本身不难,但容易因单位、括号、常量精度出错。
典型写法(返回单位:千米):
SELECT 6371 * ACOS( COS(RADIANS(lat1)) * COS(RADIANS(lat2)) * COS(RADIANS(lng2) - RADIANS(lng1)) + SIN(RADIANS(lat1)) * SIN(RADIANS(lat2)) ) AS distance_km FROM locations;
关键细节:
-
RADIANS()必须套在所有角度值外,别漏掉任何一个 - 地球半径用
6371(km)还是6371000(m)要和业务单位对齐 - 如果
lat1/lng1是固定点,建议提前算好COS(RADIANS(...))等中间值,避免重复计算拖慢查询 - 这个公式假设地球是完美球体,对跨洲级距离误差约 0.5%,城市内定位基本够用
为什么不用 ST_Distance?
ST_Distance 在 MySQL 中默认按平面欧氏距离算,单位是“度”(degree),不是米也不是千米——它把经纬度当成直角坐标系里的 x/y,完全忽略地球曲率。哪怕你传的是 POINT,只要没指定球面计算,结果就是错的。
比如两点都在赤道附近,ST_Distance 可能返回 0.5,但这 0.5 是“度”,换算成距离要看纬度带,不能直接当千米用。
- MySQL 8.0.17+ 开始支持
ST_Distance(..., unit => 'metre'),但前提是 SRID 支持单位(如 4326),且仍不如ST_Distance_Sphere明确 - 永远不要对地理坐标用
ST_Distance代替ST_Distance_Sphere
实际查询中绕不开的坑
真实业务里,光算距离不够,往往还要查“5km 内的所有门店”。这时候容易犯三个错:
- 没加粗筛条件:先用简单边界框(
WHERE lng BETWEEN ? AND ? AND lat BETWEEN ? AND ?)快速排除大部分行,再对候选集算精确距离——否则ST_Distance_Sphere或 Haversine 会每行都执行,慢到无法接受 - 索引失效:如果 WHERE 条件里对经纬度字段用了函数(如
ABS(lng - ?)),SPATIAL或普通 B-tree 索引都会失效 - 单位混淆:前端传的是米,后端 SQL 却按千米算,或者反过来,导致范围查空或爆炸
最稳妥的做法是:先用矩形范围初筛(覆盖目标圆的最小外接矩形),再用 ST_Distance_Sphere 精筛,最后 ORDER BY ... LIMIT 控制返回量。


















