thinkphp6实现“附近的人”功能需存储经纬度并建联合索引,用haversine公式在sql中计算球面距离并按距离排序筛选。

ThinkPHP6 实现“附近的人”功能,核心是基于用户地理位置(经纬度)计算距离,并按距离排序筛选。关键不在 TP6 特性,而在于地理距离算法选型、数据库设计与查询优化。
1. 数据库中存储经纬度并建立索引
用户表需包含 latitude(纬度)、longitude(经度)两个字段,类型建议为 DECIMAL(10,8)(足够精确到几米)或 DOUBLE。避免用字符串或整数存储。
为提升查询效率,在这两个字段上建立联合索引(尤其当数据量大时):
ALTER TABLE `user` ADD INDEX `idx_lat_lng` (`latitude`, `longitude`);
2. 使用“球面余弦定理”或“Haversine 公式”计算距离
MySQL 原生不支持地理距离函数(除非启用 GIS 扩展),推荐在 SQL 中直接使用 Haversine 公式计算两点间球面距离(单位:公里):
示例 SQL(查当前用户(lat=39.9042, lng=116.4074)5公里内其他用户):
SELECT id, nickname,
ROUND(
6371 * ACOS(
COS(RADIANS(39.9042)) * COS(RADIANS(latitude)) *
COS(RADIANS(longitude) - RADIANS(116.4074)) +
SIN(RADIANS(39.9042)) * SIN(RADIANS(latitude))
), 2
) AS distance
FROM `user`
WHERE latitude IS NOT NULL AND longitude IS NOT NULL
HAVING distance TP6 中可用 Query 类或 Db::raw() 封装该逻辑,注意参数需安全绑定防止 SQL 注入。
3. 用“矩形过滤+精确排序”提升性能(推荐)
纯 Haversine 查询无法利用索引,大数据量下会全表扫描。更优做法是先用经纬度范围粗筛(矩形框),再在结果中精确算距排序:
- 根据目标点和最大距离(如 5km),估算经纬度偏移范围(纬度1°≈111km,经度1°≈111×cos(纬度)km)
- 先 WHERE 过滤出 lat∈[lat−Δ, lat+Δ] 且 lng∈[lng−Δ, lng+Δ] 的记录
- 再对子集用 Haversine 计算真实距离并排序
TP6 可这样写(简化示意):
$lat = 39.9042; $lng = 116.4074; $max_km = 5;
$latDelta = $max_km / 111.0;
$lngDelta = $max_km / (111.0 * cos(deg2rad($lat)));
<p>$users = Db::name('user')
->where('latitude', 'between', [$lat - $latDelta, $lat + $latDelta])
->where('longitude', 'between', [$lng - $lngDelta, $lng + $lngDelta])
->field('id,nickname,latitude,longitude')
->select()
->toArray();</p><p>// PHP 端二次过滤 + 排序(小数据量可行)或拼接带 Haversine 的 SQL 子查询</p>4. 可选:升级 MySQL 5.7+ 启用 ST_Distance_Sphere
若数据库支持 GIS(如 MySQL ≥5.7.6),可将经纬度存为 POINT 类型,并用空间函数:
ALTER TABLE user ADD COLUMN location POINT; UPDATE user SET location = POINT(longitude, latitude); ALTER TABLE user ADD SPATIAL INDEX(location); <p>SELECT id, nickname, ROUND(ST_Distance_Sphere(location, POINT(116.4074, 39.9042)) / 1000, 2) AS distance FROM user WHERE ST_Distance_Sphere(location, POINT(116.4074, 39.9042)) </p>
此方式性能更好、语义清晰,但需调整表结构与迁移数据。











