st_distance返回平面欧氏距离(单位非米或度),仅适用于mysql 5.7.6+、innodb引擎、point类型字段且srid=4326;需配合st_distance_sphere获取真实米制距离,并用mbrcontains等前置过滤触发空间索引。

ThinkPHP 6 原生调用 ST_Distance 报错“function does not exist”
MySQL 5.7+ 才原生支持 ST_Distance,但默认用的是 MyISAM 引擎或低版本 MySQL 时,函数根本不可用。ThinkPHP 不会自动帮你检测引擎或版本,直接写进去就报错。
实操建议:
- 先确认 MySQL 版本:
SELECT VERSION();,必须 ≥ 5.7.6 - 确认表使用
InnoDB引擎且字段是POINT类型(不是VARCHAR存经纬度) - 在 ThinkPHP 查询中,不能用
whereRaw直接拼ST_Distance(geo, POINT(?, ?))—— 参数绑定会被当成字符串字面量,导致函数接收非法参数 - 正确写法是用
Db::raw()包裹整个表达式,并手动处理坐标参数类型(确保是 float):Db::name('shop') ->field('id, name, ST_Distance(geo, POINT(:lng, :lat)) AS distance') ->bind(['lng' => $lng, 'lat' => $lat]) ->order('distance', 'asc') ->select();
用 ST_Distance 算出来的距离单位是“米”还是“度”?
答案:都不是——它返回的是**平面欧氏距离,单位是“度”的平方根”,毫无地理意义。直接排序可以,但数值不能当公里数用。
常见错误现象:查出来距离最小是 0.0012,以为是 1.2 公里,实际可能是 130 米(取决于所在纬度)。
实操建议:
- 如果只要「按远近排」,
ST_Distance没问题,性能比ST_Distance_Sphere高不少 - 如果要显示“距您 2.3 公里”,必须换
ST_Distance_Sphere(MySQL 5.7.6+),它返回单位是**米**:ST_Distance_Sphere(geo, POINT(:lng, :lat))
-
ST_Distance_Sphere要求两个参数都是POINT,且 SRID 必须是 4326(WGS84),建表时就得指定:geo POINT NOT NULL SRID 4326
- 没设 SRID 会导致函数返回
NULL,但 ThinkPHP 不报错,只会查出空距离
ThinkPHP 的 whereGeoNear 和原生 ST_Distance 能混用吗?
不能。ThinkPHP 内置的 whereGeoNear 是为 MongoDB 设计的,对 MySQL 完全无效——调用后既不报错也不生效,SQL 里压根不出现地理相关条件。
使用场景:只有你项目同时用了 MySQL 和 MongoDB,且误把 MySQL 表当 Mongo 表操作时,才会掉进这个坑。
实操建议:
- 删掉所有
whereGeoNear调用,MySQL 地理查询只能靠whereRaw+Db::raw() - 如果想封装复用,自己写个查询构造器方法,不要依赖框架的“地理方法”别名
- 注意
ST_Distance在 WHERE 中无法走空间索引,必须配合MBRContains或ST_Within先圈范围,否则全表扫描很慢
为什么加了 ORDER BY ST_Distance(...) 却不走空间索引?
因为 MySQL 的空间索引(R-Tree)只加速 WHERE 条件中的范围判断(如 ST_Within、ST_Distance_Sphere ),不参与 <code>ORDER BY 排序优化。
性能影响:10 万条数据,没加前置过滤,每次查询都要算全部点的距离,可能 2s+。
实操建议:
- 必须加一层“粗筛”:
->whereRaw('ST_Distance_Sphere(geo, POINT(:lng, :lat)) 这样能触发空间索引快速定位候选集 - 再在结果里用
ORDER BY ST_Distance_Sphere(...)精排,实际计算量下降 90% 以上 - 避免用
LIMIT 10代替范围筛选——LIMIT是最后执行的,前面仍全量计算 - 测试时用
EXPLAIN看key列是否出现你的空间索引名(比如spatial_geo)
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










