暴力遍历+haversine在百万数据上必然卡死,因每次查询需对全部点执行百万次含4次sin/cos、2次sqrt、1次atan2的浮点运算,CPU缓存不友好且无法利用数据库索引;MySQL/PostgreSQL的B+树对球面距离范围查询无效,粗筛后仍需逐个精确计算,误差大、漏点多,Python中受GIL限制无法并行加速。

直接结论:100万级POI查附近500米,用rtree比暴力计算快约1500倍;用geohash前缀查询比rtree快2–3倍,但需额外做距离校验。
为什么暴力遍历+haversine在百万数据上必然卡死
每次查询都要对全部点调用三角函数:一次haversine含4次sin/cos、2次sqrt、1次atan2。100万次就是百万级浮点运算,CPU缓存不友好,且无法走任何数据库索引。
- MySQL/PostgreSQL对
(lat, lng)组合字段建B+树索引,对范围查询(如BETWEEN)几乎无效——因为球面距离不是线性可分的 - 即使加了
WHERE lat BETWEEN ? AND ? AND lng BETWEEN ? AND ?做粗筛,仍要对筛选结果逐个算距离,误差大(高纬度经度压缩)、漏点多 - 内存中Python列表遍历+计算,GIL限制下无法并行加速
用rtree库做内存内空间索引的实操要点
rtree本质是R*-Tree的Python绑定,适合中等规模(百万级以内)、更新不频繁、需精确几何判断(如相交、包含)的场景。
- 安装:
pip install rtree(注意Windows需先装libspatialindex二进制) - 插入时必须提供四元组
(minx, miny, maxx, maxy),即每个点的MBR——对单点,就填成(lng, lat, lng, lat)(注意:rtree坐标系是(x,y)对应(lng,lat)) - 查询用
.intersection((min_lng, min_lat, max_lng, max_lat))返回候选ID,再对这些ID做精确haversine过滤 - 不要把整个GeoJSON Polygon塞进去当对象——
rtree只索引MBR,不理解几何语义;复杂形状需先外切矩形再二次校验
用geohash实现亚毫秒级前缀查询的关键细节
Geohash把二维位置映射为字符串,利用B+树或Redis的字典序索引能力,实现O(log n)甚至O(1)定位。
立即学习“Python免费学习笔记(深入)”;
- 精度选
geohash_length=6(约±1.2km误差)适合城市POI;length=7(±150m)适合门店级;超过8后字符串变长、索引膨胀,收益递减 - 不能只查自身
geohash值——必须调用geohash.expand(lat, lng, radius)获取周边8个邻接格子(共9个),否则边缘点会漏掉 - 数据库里要把
geohash设为前缀索引(MySQL)或作为Redis Hash的key前缀,避免全表扫描 - 查出的候选集仍需用
haversine重排并截断——因为Geohash格子是矩形,而“附近”是圆形区域,格子角上的点可能远超半径
选rtree还是geohash?看这三点
真正影响落地效果的,不是理论性能,而是你手头的数据形态和更新频率。
- 如果POI极少变动(如静态地图数据),且需要支持“某个多边形区域内所有点”这类查询——选
rtree,它原生支持几何谓词 - 如果查询90%是“附近X米”,且数据高频写入(如实时上报的设备位置),优先用
geohash——字符串索引天然支持分片、缓存、跨服务共享 - 别忽略冷启动成本:
rtree构建索引是同步阻塞的,100万点约耗300ms;geohash只需给每条记录加一列字符串,INSERT时实时计算即可
最常被跳过的环节:无论用哪种,都得在应用层做二次距离校验和排序。Geohash给出的是“可能在附近”的粗筛结果,不是最终答案。


















