MySQL 5.7.6+ 应优先使用 ST_Distance_Sphere() 计算球面距离,要求 POINT(lng, lat) 格式并建空间索引;旧版本或非POINT字段则手写带截断的Haversine公式。

MySQL 5.7+ 直接用 ST_Distance_Sphere() 最省事
如果你的 MySQL 版本 ≥ 5.7.6,且坐标字段是 POINT 类型(推荐存为 POINT(longitude, latitude)),ST_Distance_Sphere() 就是唯一该用的函数——它返回单位为米的球面距离,精度高、语法直白、无需手写 Haversine 公式。
常见错误是把经纬度顺序搞反:POINT 要求先经度后纬度(POINT(lng, lat)),而很多人习惯写成 POINT(lat, lng),会导致结果偏差极大(比如算出 1000km 实际只有 10km)。
示例:查离 (116.48, 39.92) 最近的 5 家店
SELECT id, name,
ST_Distance_Sphere(POINT(116.48, 39.92), location) AS distance_m
FROM stores
WHERE location IS NOT NULL
ORDER BY distance_m
LIMIT 5;-
location字段必须是POINT类型,并建立空间索引:ALTER TABLE stores ADD SPATIAL INDEX(location); - 如果原始数据是两个分离的
DECIMAL字段(如lng和lat),得先构造:POINT(lng, lat),不能直接传标量 - 该函数不支持
NULL坐标,查询前务必过滤或用COALESCE处理
MySQL 5.6 或字段不是 POINT 类型?手写 Haversine 更可控
老版本或字段是普通浮点数时,硬编码 Haversine 公式反而比类型转换更稳。注意所有角度必须转为弧度,PI()/180 是关键,漏掉就全错。
典型错误:用 ACOS 但没限制输入范围,当两点极近时 cos 值略大于 1,ACOS 返回 NULL。得用 LEAST(GREATEST(...)) 截断。
示例:计算用户表中每个人到某坐标的直线距离(单位:千米)
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
SELECT id, name,
6371 * ACOS(
LEAST(GREATEST(
COS(RADIANS(39.92)) * COS(RADIANS(lat)) *
COS(RADIANS(lng) - RADIANS(116.48)) +
SIN(RADIANS(39.92)) * SIN(RADIANS(lat)),
-1), 1)
) AS distance_km
FROM users;- 6371 是地球平均半径(km),若要米就换 6371000
-
RADIANS()必须套在每个lat/lng上,不能只对外层用一次 - 没有空间索引,纯计算,大数据量时性能差;建议加
WHERE先粗筛(比如ABS(lat - 39.92) )
为什么不用 ST_Distance()?它根本不适合地理距离
ST_Distance() 计算的是平面欧氏距离,单位取决于坐标系——如果你的 POINT 没显式指定 SRID(比如 SRID 4326),它默认按“度”算,结果毫无地理意义(比如 0.01 度在赤道≈1.1km,在北京≈0.78km)。
哪怕你强制设了 SRID 4326,ST_Distance() 在 MySQL 中仍不保证球面计算(依赖底层 GEOS 实现,行为不稳定),官方文档也明确推荐用 ST_Distance_Sphere() 替代。
- 执行
SELECT ST_SRID(location) FROM stores LIMIT 1;看是否为 4326;不是就别信ST_Distance()的结果 - 即使 SRID=4326,
ST_Distance()返回值单位仍是“度”,不是米——除非你手动乘以近似换算系数,但那又退化成粗略估算
性能和精度取舍:什么时候该建空间索引?
空间索引只对 POINT + ST_Distance_Sphere() 有效,对 Haversine 手写公式完全无效。但它对“附近 N 个点”类查询提升巨大,尤其百万级以上数据。
容易被忽略的坑:空间索引要求字段类型严格为 POINT,且不能有函数包装(比如 ST_PointFromText(CONCAT('POINT(', lng, ' ', lat, ')')) 就无法走索引)。
- 建索引前确认字段类型:
SHOW COLUMNS FROM stores LIKE 'location';输出类型应为point - 批量导入时用
ST_GeomFromText('POINT(116.48 39.92)', 4326)而非字符串拼接,确保 SRID 正确 - 空间索引不支持
ORDER BY ... LIMIT的优化提示,MySQL 会自动选择是否使用,没法强制
实际用的时候,先看版本和字段类型,再决定走哪条路。空间函数看着高级,但类型不对、顺序颠倒、索引没建,结果可能比手算还离谱。

















