查询"附近的人"没那么难:PostGIS地理查询实操
“帮我找附近 1 公里的奶茶店”——这种需求几乎每个带位置的应用都要做。很多人一上来就用经纬度算距离再 ORDER BY,数据一大就慢得要命。其实 PostgreSQL 装个 PostGIS 扩展,地理查询就是一行空间函数的活儿。我把这套用在了商铺推荐里,记录一下。

一、空间数据和普通字段不一样
经纬度看着是两个数字,但直接拿来做距离计算,得考虑地球是圆的、度数不等于米。PostGIS 提供了 geometry 类型和一堆空间函数,还支持 GIST 空间索引,让"范围内筛选"走索引而不是全表扫。这才是它快的根本原因。
二、建表与索引
给商铺表加一个 geom 字段存坐标点,并建 GIST 索引:
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE TABLE shops (
id serial PRIMARY KEY,
name text,
geom geometry(Point, 4326) -- 4326 = WGS84 经纬度坐标系
);
CREATE INDEX idx_shops_geom ON shops USING GIST (geom);
4326 是常用的经纬度坐标系 SRID,前后端拿到的 GPS 坐标基本都是这个,别乱填别的。
三、一条 SQL 圈出周边
以用户坐标 (116.40, 39.90) 为中心,找 0.01 度(约 1 公里)内的店,按距离排序:
SELECT name,
ST_Distance(
geom,
ST_SetSRID(ST_MakePoint(116.40, 39.90), 4326)
) AS dist
FROM shops
WHERE ST_DWithin(
geom,
ST_SetSRID(ST_MakePoint(116.40, 39.90), 4326),
0.01
)
ORDER BY dist
LIMIT 20;
ST_DWithin 做的是"是否在给定的距离内",配合 GIST 索引能快速圈定候选,比先算全部距离再过滤高效得多。
四、性能上的几个细节
| 做法 | 是否走索引 | 备注 |
|---|---|---|
ST_DWithin + GIST 索引 |
是 | 推荐,范围筛选走索引 |
ST_Distance(...) < x |
否(通常) | 先计算再过滤,大表慢 |
| 存经纬度两列自己算 Haversine | 否 | 麻烦且易错 |
踩过的最大的坑:用了 ST_Distance < 阈值 却没走索引,查询慢十倍。换成 ST_DWithin 后立刻起飞。另外距离单位要看 SRID,4326 下 ST_Distance 返回的是度数,要米的话用 ST_Distance(..., true) 或统一投影到 3857。
总结一下
"附近的人/店"本质是空间范围查询,PostGIS 把这件事做得既标准又快。记住两句话就够用:坐标存 geometry 字段、范围筛选用 ST_DWithin 并配 GIST 索引。剩下的距离排序、分页都是 SQL 基本功。主流云数据库大多提供 PostGIS 兼容能力,这套写法基本能直接迁移。
- 点赞
- 收藏
- 关注作者
评论(0)