推荐用 Haversine 公式计算两个经纬度间距离,因其在 PHP 中稳定、可读、误差可控,而直接用 sqrt() + pow() 手写球面距离公式易出错且忽略地球扁率。

PHP 怎么算两个经纬度之间的距离
直接用 sqrt() + pow() 手写球面距离公式容易出错,且不考虑地球扁率;推荐用 Haversine 公式,它在 PHP 中稳定、可读、误差可控(
常见错误是把角度当弧度传给 sin()/cos() —— PHP 的三角函数一律要 deg2rad() 转换。
- 输入经纬度单位必须是十进制度(如
39.9042,116.4074),不是度分秒 - 结果默认是千米,乘以 1000 得米;若需英里,最后除以 1.60934
- 别在循环里反复调用
deg2rad(),提前转好再传入计算函数
function haversineDistance($lat1, $lng1, $lat2, $lng2) {
$lat1 = deg2rad($lat1);
$lng1 = deg2rad($lng1);
$lat2 = deg2rad($lat2);
$lng2 = deg2rad($lng2);
<pre class="brush:php;toolbar:false;">$dLat = $lat2 - $lat1;
$dLng = $lng2 - $lng1;
$a = sin($dLat/2) * sin($dLat/2) +
cos($lat1) * cos($lat2) *
sin($dLng/2) * sin($dLng/2);
$c = 2 * asin(sqrt($a));
return $c * 6371; // 千米}
为什么不能直接用 MySQL 的 ST_Distance_Sphere() 查附近的人
看起来很美:一条 SQL 就能返回 5km 内的用户。但实际线上一跑就慢——因为 ST_Distance_Sphere() 无法有效利用空间索引(除非你建了 POINT 字段 + SPATIAL 索引,且查询必须用 ST_Within() 或 ST_Distance() 配合 MBRContains() 做预剪枝)。
更现实的问题是:老项目表结构早定型,用户表里只有 lat 和 lng 两个 DOUBLE 字段,强行改 POINT 成本高、风险大。
- 纯
WHERE ST_Distance_Sphere(point1, point2) 是全表扫描,哪怕有索引也无效 - MySQL 5.7+ 支持
ST_Distance_Sphere(),但 5.6 及更早版本不支持,兼容性差 - 即使加了
SPATIAL索引,查询条件里没用MBR边界框兜底,优化器大概率弃用索引
GeoHash 怎么在 PHP 里生成并用于 MySQL 查询
GeoHash 把二维经纬度编码成字符串(如 "wx4g0"),越长精度越高;关键是:前缀相同的 GeoHash 表示地理位置相近——这使得可以用普通 B-Tree 索引加速范围查询。
PHP 侧只需生成目标坐标的 GeoHash 和它的 8 个邻接格子(防止跨格误差),然后拼成 IN 查询;MySQL 侧字段存 VARCHAR(12) 并加普通索引即可。
- 用
phpgeo库最省事:composer require mjaschen/phpgeo,调Geohash::encode() - 精度选 6~7 位(对应约 1.2km / 0.3km),太短查不准,太长索引区分度下降
- 必须同时查 9 个邻接 GeoHash(中心 + 上下左右 + 四角),否则在格子边界会漏人
- 查完结果还得用 Haversine 过滤一遍,剔除落在邻接格但实际超距的点
// 示例:查 1km 内用户(GeoHash 长度 6)
$geohash = Geohash::encode($lat, $lng, 6);
$neighbors = Geohash::neighbors($geohash); // 返回 8 个邻接 hash
$allHashes = array_merge([$geohash], $neighbors);
<p>$placeholders = str_repeat('?,', count($allHashes) - 1) . '?';
$stmt = $pdo->prepare("SELECT id, lat, lng FROM users WHERE geohash IN ($placeholders)");
$stmt->execute($allHashes);</p>
“附近的人”接口响应慢,第一个该查什么
不是立刻加缓存或上 Redis,先看 MySQL 的 EXPLAIN 输出里 type 是不是 range 或 ref,key 列有没有命中索引——90% 的慢查源于 GeoHash 字段没索引,或用了 LIKE 'wx4g0%' 却忘了加前缀索引(INDEX(geohash(6)))。
- 如果
EXPLAIN显示type: ALL,说明完全没走索引,先加ALTER TABLE users ADD INDEX idx_geohash (geohash); - 如果用了
ORDER BY distance LIMIT 20,但distance是计算字段,MySQL 无法用索引排序,得改成先按 GeoHash 粗筛,再 PHP 排序 - 用户量过 100 万后,单靠 GeoHash + B-Tree 会变慢,这时才需要考虑 Redis 的
GEO命令或 PostGIS
GeoHash 不是银弹:它把球面映射到矩形,极地和国际日期变更线附近会失真;如果你的业务集中在挪威或斐济,得单独做坐标偏移补偿。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











