postgresql + postgis 是目前最直接支持按距离 join 的组合,核心是用 st_distance 作 join 条件或 where 过滤;sql 标准无原生距离 join 语法,必须依赖空间扩展函数。

用ST_Distance配合JOIN做地理邻近查询
PostgreSQL + PostGIS 是目前最直接支持「按距离 JOIN」的组合,核心是把 ST_Distance 当作 JOIN 条件或 WHERE 过滤项。别试图在纯 SQL 标准里找“距离 JOIN”语法——它不存在,必须依赖空间扩展函数。
常见错误是写成 ON ST_Distance(a.geom, b.geom) 却没建空间索引,导致全表扫描,10万条记录查一次要几十秒。
- 必须给参与计算的几何列(如
geom)建立GIST索引:CREATE INDEX idx_locations_geom ON locations USING GIST (geom); - 距离单位取决于坐标系:如果用的是 WGS84(EPSG:4326),
ST_Distance返回的是“度”,不是米;要用ST_Distance(geom::geography, geom::geography)转成米 - 想查“每个点最近的3个邻居”?
LATERAL JOIN比子查询更高效,避免笛卡尔积
用LATERAL JOIN实现“为每个A找最近N个B”
这是实际业务中最常卡住的场景:比如“查每个门店周围5公里内的竞品店,最多取3家”。用普通 JOIN 会先做交叉连接再过滤,数据量大时直接 OOM;LATERAL 让子查询能引用左表字段,且可结合 ORDER BY ... LIMIT 利用索引快速截断。
SELECT a.id AS store_id, b.id AS competitor_id,
ST_Distance(a.geom::geography, b.geom::geography) AS distance_m
FROM stores a
LEFT JOIN LATERAL (
SELECT id, geom
FROM competitors b2
WHERE ST_DWithin(a.geom::geography, b2.geom::geography, 5000)
ORDER BY a.geom::geography b2.geom::geography
LIMIT 3
) b ON true;
注意两点:ST_DWithin 是索引友好的距离预过滤(比 ST_Distance 快一个数量级),而 <code> 是 KNN 操作符,依赖 GIST 索引直接走最近邻搜索,不是先算全部距离再排序。
PostgreSQL 18.4 官方 Ubuntu 安装包现已发布,这是目前最新的稳定版本。推荐通过官方 APT 仓库安装:先执行 sudo apt update 更新索引,再运行 sudo apt install postgresql-18 即可完成部署。新版本引入了异步 I/O 子系统,在顺序扫描与 VACUUM 场景下性能提升显著,同时支持 UUID v7 原生生成函数与虚拟生成列。
MySQL / SQL Server 怎么办?没有LATERAL和操作符
它们不支持地理 KNN 原生加速,只能退化为“先圈范围、再算距离、最后排序”。性能差,但能用:
- MySQL 8.0+ 支持
ST_DistanceSphere,但无法利用空间索引优化ORDER BY ST_DistanceSphere(...),必须加WHERE ST_DWithin(...)配合矩形预筛选(ST_Contains(ST_MakeEnvelope(...), geom)) - SQL Server 用
geography::STDistance(),同样要先用STIntersects+ 方形缓冲区缩小候选集,否则TOP 3 ORDER BY .STDistance()会扫全表 - 所有非 PostGIS 方案都建议把“距离计算”移到应用层:查出 1km 内所有点(靠索引快),再用 Haversine 公式在代码里算精确球面距离并排序
为什么不能直接用Haversine公式写在ON条件里?
因为 Haversine 是纯数学表达式,数据库无法为其建索引,ON 6371 * acos(...) 这种写法会让 JOIN 变成嵌套循环暴力匹配。哪怕只有 1000×1000 条记录,也要算 100 万次三角函数——CPU 成瓶颈,比磁盘还慢。
真正关键的不是“怎么算距离”,而是“怎么跳过绝大多数计算”。PostGIS 的 和 ST_DWithin 背后是 R-Tree 索引剪枝,而 Haversine 没有索引支撑。线上服务一旦并发稍高,这种写法就会拖垮整个数据库。










