MySQL 5.7+ 应优先使用 ST_Distance_Sphere 计算球面距离,精度高、性能好;需确保 location 字段为 POINT 类型并建立 SPATIAL 索引,查询时用 ST_PointFromText 构造点,GeoHash 仅适用于粗筛,不可替代精确计算。

MySQL 5.7+ 直接用 ST_Distance_Sphere 最省事
PHP 本身不处理地理距离计算,真正干活的是数据库。别在 PHP 里手算经纬度球面距离,精度低、性能差、还容易写错。MySQL 5.7 起原生支持地理空间函数,ST_Distance_Sphere 就是专干这事的——它自动按 WGS84 椭球模型算两点间真实地表距离(单位:米)。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 确保字段类型是
POINT,且加了SPATIAL索引:ALTER TABLE places ADD COLUMN location POINT NOT NULL;
ALTER TABLE places ADD SPATIAL INDEX(location);
- 插入数据必须用
ST_PointFromText('POINT(116.48 39.92)'),不能直接插字符串或数组 - 查询时用
ST_Distance_Sphere+ST_PointFromText:SELECT id, name FROM places WHERE ST_Distance_Sphere(location, ST_PointFromText('POINT(116.48 39.92)')) <= 1000; - 注意:
ST_Distance_Sphere的第二个参数必须是POINT类型,传字符串会静默失败,返回NULL
GeoHash 只适合「粗筛」,别当精确距离用
GeoHash 是把经纬度编码成字符串,前缀相同表示位置相近。但它本质是矩形网格划分,越靠近极点或国际日期变更线,误差越大;而且“距离近”不等于“GeoHash 前缀长”,两个点可能只差几米但 GeoHash 完全不同(跨格子边界)。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- PHP 侧可用
geoPHP库或手写简单编码函数,但只用于生成缓存键或预过滤:$geohash = geo_encode(39.92, 116.48, 7); // 7 位精度约 ±1.2km
- 数据库里建普通 B-Tree 索引在
geohash字段上,查WHERE geohash LIKE 'wx4g0e%'快,但结果只是候选集 - 后续必须用
ST_Distance_Sphere或 PHP 的rad2deg/acos公式二次精排,否则误差可能达几百米 - 别用 GeoHash 做排序依据:
ORDER BY geohash没地理意义
PHP 里手动算距离?只在没空间索引时救急
如果 MySQL 版本太老(
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 别抄网上缺
deg2rad的残缺代码,正确写法:$lat1 = deg2rad($lat1); $lon1 = deg2rad($lon1); // 必须转弧度
- PHP 7.2+ 可用
acos(cos($lat1) * cos($lat2) * cos($lon2 - $lon1) + sin($lat1) * sin($lat2)) * 6371000(结果单位米) - 单次计算快,但查 1 万条数据就得算 1 万次——务必先用经纬度范围粗筛:
WHERE lat BETWEEN ? AND ? AND lon BETWEEN ? AND ?
,再对结果集 PHP 计算 - 注意:
acos输入必须在 [-1,1] 内,超限会返回NAN,要加max(-1, min(1, $x))保护
空间索引失效的三个典型原因
明明加了 SPATIAL 索引,EXPLAIN 却显示没走索引?大概率栽在这三处。
-
WHERE ST_Distance_Sphere(location, @point) < 1000—— 函数出现在 WHERE 左侧,索引失效。必须改写为:WHERE ST_Within(location, ST_Buffer(@point, 1000))
或更稳妥的:WHERE MBRContains(ST_Envelop(@point, 1000), location)
- 字段用了
CAST(location AS CHAR)或CONVERT—— 空间类型被强转,索引立即作废 - 连接查询中,
JOIN ... ON ST_Distance_Sphere(a.loc, b.loc) < 1000—— 空间函数无法下推,全表扫描不可避免
GeoHash 和空间索引不是二选一,而是分层协作:GeoHash 做缓存/降级兜底,空间索引做主路精准查询。最常被忽略的是——哪怕用了 ST_Distance_Sphere,如果没配合 MBRContains 或范围前置过滤,性能照样崩。











