不能直接用 abs(lat1 - lat2) + abs(lng1 - lng2) 计算距离,因为地球是球面,经纬度非平面直角坐标;赤道与高纬度地区经度1度对应距离差异大,导致误差随距离和纬度快速放大。st_distancesphere() 是 mysql 5.7.6+ 原生球面距离函数,单位米、精度高,需用 point(lng, lat) 构造并建空间索引。

为什么不能直接用 ABS(lat1 - lat2) + ABS(lng1 - lng2) 算距离
这种写法看着简单,实际结果完全失真——地球是球面,经纬度不是平面直角坐标。赤道上1度经度 ≈ 111km,但到了北纬60°,1度经度只剩约55km;而纬度方向相对稳定(1度≈111km),但叠加后误差会随距离和纬度快速放大。用它做“附近5公里”筛选,可能漏掉真实在范围内的点,也可能拉进几十公里外的干扰项。
ST_DistanceSphere() 是 MySQL 5.7.6+ 最省心的选择
它原生支持球面距离计算,单位是米,精度高且无需手写公式。前提是两个点都用 POINT(lng, lat) 构造(注意:POINT 参数顺序是 lng, lat,不是 lat, lng)。
- 确保字段类型是
POINT,并加空间索引:ALTER TABLE locations ADD COLUMN coord POINT SRID 4326; CREATE SPATIAL INDEX idx_coord ON locations(coord); - 查询示例:
SELECT id, name FROM locations WHERE ST_DistanceSphere(coord, POINT(-73.9857, 40.7484)) (查纽约时代广场5公里内) - SRID 必须设为
4326(WGS84地理坐标系),否则ST_DistanceSphere()返回NULL或报错ER_NOT_IMPLEMENTED_FOR_SPATIAL_TYPES
兼容老版本 MySQL(
用 ACOS、COS、SIN 组合实现,核心是把经纬度转弧度,再套球面余弦公式。性能不如 ST_DistanceSphere(),且无法利用空间索引,大数据量时务必加 WHERE 先粗筛。
- 先用经纬度范围快速过滤(避免全表扫描):
lat BETWEEN ? - 0.045 AND ? + 0.045(≈5km 纬度跨度),lng BETWEEN ? - 0.045 / COS(RADIANS(?)) AND ? + 0.045 / COS(RADIANS(?)) - 再用 Haversine 精算:
SELECT id, name, 6371 * ACOS(COS(RADIANS(40.7484)) * COS(RADIANS(lat)) * COS(RADIANS(lng) - RADIANS(-73.9857)) + SIN(RADIANS(40.7484)) * SIN(RADIANS(lat))) AS distance_km FROM locations WHERE ... HAVING distance_km - 注意:
6371是地球平均半径(km),若要米就用6371000;所有角度必须转RADIANS(),否则结果全错
别忽略单位、精度和索引这三处硬伤
很多人调通公式就以为完事,结果上线后慢或不准——问题常出在这三点:
- 单位混淆:
ST_DistanceSphere()返回米,Haversine 手算默认是千米,混用会导致条件写成却实际查了5米 - 经纬度字段类型用
DOUBLE就够,但必须保证小数位 ≥ 6(精度到约0.1米),FLOAT在长距离计算中会因舍入误差导致100米级偏差 -
ST_DistanceSphere()虽快,但没空间索引时仍是全表扫描;而 Haversine 再快也绕不开先用普通 B-tree 索引筛一遍经纬度范围
真正上线前,拿真实数据跑 EXPLAIN 看是否走了索引,再用已知坐标的两点手动验算结果是否落在合理区间——球面距离没法靠感觉判断对错。











