sql中仅用sin/cos无法直接得出准确球面距离,因缺少地球半径、弧度转换及坐标系处理;必须配合radians()、acos()、6371公里半径及边界校验(如least/greatest)才能安全计算。

SQL里直接用SIN和COS算两点距离行不通
不能只靠SIN和COS函数就得到准确的球面距离。它们只是三角函数,本身不包含地球半径、弧度转换、坐标系投影等必要要素。直接套公式如 SIN(lat1) * SIN(lat2) + COS(lat1) * COS(lat2) * COS(lon2 - lon1) 只是球面余弦定理的一部分,结果是余弦值,不是公里数,还容易因浮点精度或角度单位(度 vs 弧度)出错。
实操建议:
- 所有经纬度输入必须先转为
RADIANS()(MySQL/PostgreSQL)或手动除以180 / PI()(SQL Server) - 必须乘上地球平均半径(通常取
6371公里),否则结果无量纲 - 避免在WHERE中对经纬度列直接调用
SIN/COS做范围筛选——无法走索引,全表扫描不可避免
PostgreSQL用earth_distance比手写SIN/COS更稳
PostgreSQL 的 cube 和 earthdistance 扩展封装了球面距离计算逻辑,底层仍用三角函数,但已处理弧度、半径、边界情况(如跨国际日期变更线)。
启用后可这样写:
SELECT earth_distance( ll_to_earth(39.9042, 116.4074), -- 北京 ll_to_earth(31.2304, 121.4737) -- 上海 );
返回单位是米。相比手写SIN/COS,它省去单位转换、防NaN检查(如lat > 90)、以及反余弦值域校验(ACOS输入必须 ∈ [-1,1],浮点误差可能导致ACOS(1.0000001)报错)。
MySQL 5.7+ 应该优先用ST_Distance_Sphere而非SIN/COS
MySQL 原生SIN/COS函数不处理地理坐标系语义,而ST_Distance_Sphere明确将输入视为 WGS84 经纬度,并返回米制距离。
用法示例:
SELECT ST_Distance_Sphere( POINT(116.4074, 39.9042), -- 注意:POINT(long, lat),顺序别反 POINT(121.4737, 31.2304) );
关键细节:
-
POINT参数顺序是(longitude, latitude),和常见“lat,lng”习惯相反,填反会导致距离严重失真 - 要求字段类型为
POINT且有 SRID=4326,否则可能返回NULL或静默错误 - 若数据存的是字符串或分离的
lat/lng字段,需先用ST_PointFromText(CONCAT('POINT(', lng, ' ', lat, ')'), 4326)构造
手写Haversine公式时,ACOS是最容易崩的环节
即使坚持用SIN/COS推Haversine,核心是:ACOS的输入必须严格落在 [-1, 1] 区间。但浮点运算常导致 1.0000000000000002 这类越界值,直接报错 “Invalid argument to ACOS”。
安全写法要加截断:
-- PostgreSQL 示例 SELECT 6371 * ACOS(LEAST(GREATEST( SIN(RADIANS(lat1)) * SIN(RADIANS(lat2)) + COS(RADIANS(lat1)) * COS(RADIANS(lat2)) * COS(RADIANS(lon2) - RADIANS(lon1)), -1), 1)) AS distance_km;
这个LEAST(GREATEST(..., -1), 1)包裹必不可少。没有它,哪怕只有一行数据越界,整个查询就中断——尤其在批量计算用户附近POI时,这种错误极难定位。
地理距离计算真正麻烦的从来不是公式本身,而是单位、顺序、边界、精度这四层嵌套的隐性约束。漏掉任何一层,结果都可能差出几十公里。











