st_distance_sphere无法使用空间索引,需先用mbrcontains+st_makeenvelope矩形过滤(索引生效),再用st_distance_sphere精确计算;point字段须not null并显式建spatial索引,经纬度顺序为经度在前、纬度在后。

MySQL 5.7+ 的 ST_Distance_Sphere 不能走空间索引?
直接用 ST_Distance_Sphere 计算两点距离再加 WHERE 过滤,哪怕字段有 POINT 类型和 SPATIAL 索引,也完全不会命中索引。执行计划里 key 字段为空,全表扫描不可避免。
根本原因是 ST_Distance_Sphere 是计算密集型函数,MySQL 无法在索引树中预判其结果范围。必须先用能下推到索引层的操作“圈出候选区域”,再做精细过滤。
- 正确姿势:用
MBRContains或ST_Within配合ST_MakeEnvelope构造矩形边界,这个操作可走SPATIAL索引 - 矩形范围要略大于实际圆形需求(比如查 1km 内,先用经纬度算出 1.2km 宽的矩形),避免漏数据
- 后续再用
ST_Distance_Sphere对索引筛选后的少量结果做精确距离判断
建表时 POINT 字段和 SPATIAL 索引怎么写才有效
空间索引只支持 MyISAM 和 InnoDB(5.7.5+),但 InnoDB 是默认且推荐引擎。关键点在于:POINT 字段必须为 NOT NULL,且索引必须显式声明为 SPATIAL。
错误示例:ADD INDEX idx_location (location) —— 这建的是普通 B+ 树索引,对空间查询无效。
- 正确建表语句片段:
CREATE TABLE places ( id INT PRIMARY KEY, name VARCHAR(100), location POINT NOT NULL, SPATIAL INDEX(location) ) ENGINE=InnoDB;
- 如果已有表,添加空间索引:
ALTER TABLE places ADD SPATIAL INDEX(location); -
POINT字段插入必须用POINT(longitude, latitude),注意是“经度在前、纬度在后”,和常见 API 返回顺序相反
查询语句怎么组合 ST_MakeEnvelope 和 ST_Distance_Sphere
核心逻辑是两步:先用矩形快速缩小结果集(索引生效),再用球面距离精确过滤(CPU 计算)。别试图一步到位。
假设要查坐标 (116.48, 39.92) 周围 1000 米内的点:
- 先估算矩形边界(简单方法:1° 经度 ≈ 111km × cos(纬度),1° 纬度 ≈ 111km;1000m 对应约 0.009°)
- 构造 envelope:
ST_MakeEnvelope(POINT(116.471, 39.911), POINT(116.489, 39.929)) - 完整查询:
SELECT id, name, ST_Distance_Sphere(location, POINT(116.48, 39.92)) AS dist FROM places WHERE MBRContains( ST_MakeEnvelope(POINT(116.471, 39.911), POINT(116.489, 39.929)), location ) AND ST_Distance_Sphere(location, POINT(116.48, 39.92))
为什么不用 ST_Distance 而坚持用 ST_Distance_Sphere
ST_Distance 默认按平面欧氏距离算,单位是“度”,结果毫无地理意义。在赤道附近 0.01° 约等于 1.1km,但在高纬度可能只有 500 米,误差随纬度升高急剧扩大。
ST_Distance_Sphere 才真正按 WGS84 椭球模型计算米级距离,结果可靠。但它代价高——每行都要调用一次复杂三角函数。
- 所以必须靠前面的
MBRContains把参与ST_Distance_Sphere计算的行数压到几十或几百以内 - 如果业务允许粗略结果(比如展示“附近 1km”图标),可只用矩形过滤,跳过第二步距离计算
- 注意:MySQL 5.7.6+ 才支持
ST_Distance_Sphere,旧版本只能自己实现 Haversine 公式(性能更差,且仍需矩形预过滤)
EXPLAIN 看 key 列是否显示你的空间索引名,而不是空值。一旦发现没走索引,90% 是因为用了 ST_Distance 直接过滤,或者 POINT 字段允许 NULL。











