如何在Think中计算两点 间的地理距离 Db::raw执行GIS空间函数最佳实践|Duuu笔记
行,但需数据库支持GIS函数、函数名正确、坐标系一致;MySQL用ST_Distance_Sphere,PostgreSQL用ST_DistanceSphere,字段须为POINT类型且坐标顺序为经度纬度,推荐参数绑定防注入并确保走空间索引。
ThinkPHP里直接用
Db::raw
调GIS函数算距离行不行?
行,但得看数据库是否支持、函数名是否匹配、坐标系是否一致。MySQL 5.7+、PostgreSQL + PostGIS 都可以,SQLite 基本不行;
Db::raw
只是把字符串原样塞进 SQL,不校验语法也不转义,写错就报错。
常见错误现象:
SQLSTATE[42000]: Syntax error or access violation
或
FUNCTION xxx does not exist
,基本都是函数名拼错、没开启空间扩展、或者字段类型不是
POINT
。
MySQL 必须用
ST_Distance_Sphere
(推荐)或
ST_Distance
(平面近似,精度差)
PostgreSQL + PostGIS 要用
ST_DistanceSphere
,注意大小写和参数顺序
字段必须是
POINT
类型,且用
POINT(longitude latitude)
格式存(经度在前,纬度在后)
别直接拼字符串,用
Db::raw("ST_Distance_Sphere(location, POINT(?, ?))", [$lng, $lat])
这种带参数绑定的方式,防注入也保类型
为什么
ST_Distance_Sphere
比
haversine
手动算更可靠?
因为 MySQL 内置函数自动处理了椭球模型(WGS84),而手写 haversine 是球面近似,10km 以内误差可能不到 10 米,但跨纬度大范围(比如哈尔滨到三亚)偏差能到百米级。而且
ST_Distance_Sphere
能走空间索引(如果字段加了
SPATIAL
索引),查询快几倍。
ST_Distance_Sphere
返回单位是「米」,不用再乘 1000 或除 1000
必须确保两个
POINT
在同一坐标系(通常是 EPSG:4326),否则结果无意义
如果用
ST_Distance
(不带 Sphere),它算的是平面欧氏距离,单位是「度」,完全不能直接当公里用
ThinkPHP 的
whereRaw
或
fieldRaw
才真正需要
Db::raw
,单纯
select
字段里用就足够
在
where
条件里用
Db::raw
算距离并过滤,怎么写才不出错?
重点不是“怎么写”,而是“怎么避免全表扫描”。只要用了
ST_Distance_Sphere
就没法走 B-tree 索引,但可以先用矩形范围粗筛,再精算。
Sheet+
Excel和GoogleSheets表格AI处理工具
下载
“
PHP免费学习笔记(深入)
”;
先用
ST_Contains(ST_MakeEnvelope(...), location)
或简单
lng BETWEEN ? AND ? AND lat BETWEEN ? AND ?
锁定候选集
再在
whereRaw
里用
ST_Distance_Sphere
做最终过滤,例如:
whereRaw('ST_Distance_Sphere(location, POINT(?, ?))
别在
order by
里直接写
Db::raw('ST_Distance_Sphere(...)')
排序——除非数据量极小,否则慢得明显
如果真要按距离排序,考虑加个虚拟列(MySQL 5.7+)或用子查询缓存距离值
PostgreSQL + PostGIS 场景下,
Db::raw
要注意什么?
PostGIS 函数名首字母大写(
ST_DistanceSphere
),参数顺序是
(geom1, geom2)
,且要求 geometry 类型必须显式指定 SRID,否则返回
null
。
建表时字段定义得写成
location GEOGRAPHY(POINT, 4326)
,不能只写
GEOMETRY
查询时用
ST_SetSRID(ST_MakePoint(?, ?), 4326)
构造目标点,否则
ST_DistanceSphere
拒绝计算
ThinkPHP 默认 PDO 不启用
PDO::ATTR_EMULATE_PREPARES = false
,PostgreSQL 对二进制协议敏感,建议显式配置
错误信息像
function st_distancesphere(geography, geography) does not exist
,八成是 SRID 没对齐或类型不匹配
地理距离计算最麻烦的从来不是写那一行
Db::raw
,而是确认数据库版本、空间类型定义、坐标系一致性,以及有没有悄悄绕过索引。漏查任意一项,线上查一次就卡三秒。
