必须先用空间索引粗筛再精算,否则查询上万行会卡死;因ST_Distance_Sphere()是标量函数,MySQL优化器无法下推至索引层,即使建了SPATIAL INDEX也无效。

直接回答:别在存储过程里反复调用距离函数做 WHERE 过滤,必须先用空间索引粗筛,再精算;否则一查上万行就卡死。
MySQL 存储过程中 ST_Distance_Sphere 为什么总慢?
因为 ST_Distance_Sphere() 是标量函数,MySQL 优化器无法把它下推到索引层——哪怕你建了 SPATIAL INDEX,只要写成 WHERE ST_Distance_Sphere(...) ,就会触发全表扫描。
- 错误写法:
WHERE ST_Distance_Sphere(POINT(lng, lat), POINT(@lon, @lat)) - 正确思路:先用经纬度范围框出“可能入围”的候选集(比如
lat BETWEEN @lat - 0.05 AND @lat + 0.05),再对这个小集合调用ST_Distance_Sphere()精算 - 0.05° ≈ 5.5km(赤道附近),实际可根据目标半径动态算:km_to_deg = radius_km / 111.32
- POINT 字段必须是
POINT(longitude, latitude)类型,顺序反了会返回NULL或 0
SQL Server 存储过程里 GEOGRAPHY::Point 参数顺序别写反
GEOGRAPHY::Point() 要求参数顺序是 lat, lon, SRID,不是经度优先——这是最常翻车的地方。传反了坐标会落到南大西洋附近,距离结果完全失真。
- 错误:
GEOGRAPHY::Point(116.4074, 39.9042, 4326)(把北京的 lng 当成了 lat) - 正确:
GEOGRAPHY::Point(39.9042, 116.4074, 4326) - SRID 必须显式指定
4326,不能省略;表里存的是lng/lat字段名?调用前务必交换顺序 - 避免在循环或 JOIN 中反复构造:
SELECT ... GEOGRAPHY::Point(lat, lng, 4326).STDistance(@ref)→ 改用 CTE 预转成geo列
存储过程封装距离逻辑时,参数和字段类型必须对齐
外部传入的经纬度、表中存储的坐标、空间索引所依赖的字段类型,三者 SRID 和数据类型必须严格一致,否则 STDistance() 或 ST_Distance_Sphere() 会静默返回 0 或 NULL。
- MySQL:输入参数是字符串?先
CAST(@lon AS DOUBLE),字段必须是DECIMAL(10,8)或DOUBLE,不能是VARCHAR - SQL Server:表字段是
float?别直接塞进GEOGRAPHY::Point(),先确保精度足够(DECIMAL(12,10)更稳妥) - 坐标系不匹配(比如表里存的是 BD-09 百度坐标,却按 WGS84 计算)会导致百米级偏差,必须提前转换
- 分页查询必须带
ORDER BY distance_m, id,否则LIMIT或TOP可能漏掉最近的点
空间索引不是“建了就快”,而是有硬性前提
没建空间索引的地理查询,不管包几层存储过程,都是线性扫描。但索引本身只对特定操作生效,不是万能加速器。
- MySQL:
SPATIAL INDEX只对MBRContains()、MBRWithin()等矩形操作有效,对ST_Distance_Sphere()无加速作用 - SQL Server:
CREATE SPATIAL INDEX必须建在GEOGRAPHY类型列上,建在float的lat/lon列上等于没建 - PostgreSQL(PostGIS):
ST_DWithin()能走GIST索引,但要求两表都用GEOGRAPHY类型且 SRID 一致 - 大批量两两计算(如所有门店间距离矩阵)即使有索引也慢——这时得靠算法剪枝,比如先用 KD-Tree 或 GeoHash 分区
真正容易被忽略的点:距离函数返回 NULL 时整行会被过滤掉,而不是当 0 处理。如果用户传的经纬度是空值、超界(lat > 90)、或坐标系不匹配,结果就是无声消失——查不到数据却没报错,调试时得手动加 IS NOT NULL 检查。











