mysql 5.7.6+ 应优先使用 st_distance_sphere 计算球面距离,需将经纬度存为 srid 4326 的 point 类型并建 spatial 索引;老版本可用 haversine 公式手写,但无空间索引需矩形粗筛;切勿混用 st_distance(平面距离,单位“度”无地理意义)。

MySQL 5.7+ 直接用 ST_Distance_Sphere 最省事
如果你用的是 MySQL 5.7.6 及以上版本(推荐 8.0+),ST_Distance_Sphere 是唯一该优先考虑的方案。它原生支持地理坐标系(WGS84),自动按球面距离计算,单位是米,精度高、性能好,且语法干净。
前提是你把经纬度存成 POINT 类型,并加了 SRID 4326:
ALTER TABLE locations ADD COLUMN coord POINT SRID 4326;
UPDATE locations SET coord = ST_PointFromText(CONCAT('POINT(', lng, ' ', lat, ')'), 4326);
查某点(比如 116.48, 39.92)5 公里内的记录:
SELECT id, name,
ROUND(ST_Distance_Sphere(coord, ST_PointFromText('POINT(116.48 39.92)', 4326))) AS distance_m
FROM locations
WHERE ST_Distance_Sphere(coord, ST_PointFromText('POINT(116.48 39.92)', 4326))
- 必须显式指定 SRID 4326,否则
ST_Distance_Sphere返回NULL - 字段和常量点都得是
POINT类型,不能直接传浮点数 - 索引有效:给
coord字段加空间索引(SPATIAL INDEX(coord))能显著提速
MySQL 5.6 或没权限建空间字段?用 Haversine 公式手写 SELECT
老版本或受限环境没法用空间函数时,只能靠纯 SQL 实现 Haversine 公式。注意这不是近似值,而是标准球面距离公式,只要单位一致、角度转弧度正确,误差在几米内。
关键点全在 ROUND()、RADIANS() 和常数 6371(地球平均半径,单位 km):
SELECT id, name,
ROUND(6371 * ACOS(
COS(RADIANS(39.92)) *
COS(RADIANS(lat)) *
COS(RADIANS(lng) - RADIANS(116.48)) +
SIN(RADIANS(39.92)) *
SIN(RADIANS(lat))
), 2) AS distance_km
FROM locations
HAVING distance_km
-
lat/lng字段必须是DECIMAL或DOUBLE,不能是字符串 - 别在
WHERE里直接写这个表达式——MySQL 5.7 前不支持在WHERE中引用SELECT别名,得用HAVING(配合无GROUP BY的场景)或重复写一遍公式 - 没有空间索引,大表会全表扫描;可先用矩形粗筛(如
lat BETWEEN ? AND ? AND lng BETWEEN ? AND ?)再精算
ST_Distance 和 ST_Distance_Sphere 别混用
ST_Distance 是平面欧氏距离函数,单位取决于坐标系。如果强行对 WGS84 经纬度用它,结果是“度”单位的直线距离,数值毫无地理意义(比如 0.05 度 ≈ 5.5 km 在赤道,但在北京只≈ 4.3 km),而且不随纬度变化校正。
常见误用:
-- ❌ 错!返回的是“度”,不是米,且无视地球曲率
SELECT ST_Distance(coord, ST_PointFromText('POINT(116.48 39.92)'));
<p>-- ✅ 对!明确用球面模型,单位米
SELECT ST_Distance_Sphere(coord, ST_PointFromText('POINT(116.48 39.92)', 4326));</p>
- 只要数据是经纬度,永远避开
ST_Distance -
ST_Distance_Sphere要求两点 SRID 都是 4326;若字段没设 SRID,用ST_SRID(coord, 4326)临时绑定(但不如建表时就设好)
实际部署时最容易被忽略的三个细节
很多线上问题不是公式错,而是环境配置或数据形态没对齐:
- MySQL 默认不启用空间函数的严格模式,
ST_PointFromText遇到非法坐标(如 lng=200)会静默返回NULL,导致查询结果变少却无报错——务必在 INSERT/UPDATE 时加检查:lng BETWEEN -180 AND 180 AND lat BETWEEN -90 AND 90 - PHP/Python 等客户端取
ST_Distance_Sphere结果时,注意 MySQL 8.0.17+ 默认返回decimal类型,某些驱动可能截断小数位,建议显式CAST(... AS SIGNED)或在应用层转整型 - 如果业务需要高频查“附近的人”,单靠数据库计算很快会成为瓶颈;更稳的做法是预计算 Geohash(如 6 位精度≈1.2km),用前缀匹配快速缩小范围,再用
ST_Distance_Sphere精排











