本节摘要:空间数据有几何有属性,普通数据库存不好。本节讲空间数据库(以 PostGIS 为代表)怎么存空间数据、空间函数怎么用。
阅读完本节,你应当能够:
普通数据库存不了几何,查询"我附近 1km 的餐厅"要算距离,普通 SQL 做不到。空间数据库扩展了几何类型、空间函数、空间索引,能用 SQL 做空间查询和分析。
主流空间数据库:

-- 启用 PostGIS CREATE EXTENSION postgis; -- 建表存空间数据 CREATE TABLE restaurants ( id SERIAL PRIMARY KEY, name VARCHAR(100), geom GEOMETRY(POINT, 4326) ); -- 插入 INSERT INTO restaurants (name, geom) VALUES ('餐厅A', ST_SetSRID(ST_Point(116.4, 39.9), 4326));
小范围用 GEOMETRY + 投影坐标系,全球范围算距离用 GEOGRAPHY。
PostGIS 提供大量 ST_ 开头的空间函数:
-- 距离 SELECT ST_Distance(a.geom, b.geom) FROM ...; -- 包含 SELECT * FROM cities WHERE ST_Within(geom, (SELECT geom FROM region WHERE name='北京')); -- 相交 SELECT * FROM roads WHERE ST_Intersects(geom, flood_area); -- 缓冲区 SELECT ST_Buffer(geom, 1000); -- 1km 缓冲 -- 面积/长度 SELECT ST_Area(geom), ST_Length(geom) FROM ...;
"找某点 1km 内的餐厅":
SELECT name, ST_Distance(geom, my_point) AS dist FROM restaurants WHERE ST_DWithin(geom, my_point, 1000) -- 1km 内 ORDER BY dist;
ST_DWithin 配合空间索引能快速过滤,不用逐个算距离。
⚠️ 常见坑:用 GEOMETRY 存经纬度算距离——平面坐标算的是直线距离不是球面距离,长距离误差大。算球面距离用 GEOGRAPHY 或 ST_DistanceSphere。
💡 关键直觉:空间数据库扩展了几何类型+空间函数+空间索引。PostGIS 是首选,用 ST_ 函数做空间查询,ST_DWithin 配索引快速过滤。
PostGIS 是 PostgreSQL 的扩展,安装分两步:先在系统里安装扩展包,再在目标数据库里启用:
# Ubuntu 安装(各系统命令不同,装最新稳定版即可) sudo apt install postgresql postgis postgresql-14-postgis-3
-- 在目标数据库启用扩展 CREATE EXTENSION postgis; -- 验证安装成功 SELECT PostGIS_Version();
版本匹配值得注意:PostGIS 3.x 默认使用 GEOS 3.9+,空间函数性能和精度都有提升;如果你的老项目还在用 PostGIS 2.x,升级后部分函数签名不变,但 ST_MakeValid 等函数的行为更严格。日常开发建议跟随发行版的稳定版本,不必追最新。
PostGIS 的 ST_ 函数按用途分几组,掌握每组代表函数就能覆盖绝大多数场景:
| 分组 | 代表函数 | 用途 |
|---|---|---|
| 构造 | ST_Point、ST_GeomFromText、ST_MakePolygon | 从坐标或文本构造几何 |
| 转换 | ST_Transform、ST_SetSRID、ST_AsGeoJSON | 坐标系转换、格式导出 |
| 关系 | ST_Intersects、ST_Contains、ST_DWithin | 空间关系判断 |
| 计算 | ST_Distance、ST_Area、ST_Length、ST_Buffer | 距离面积缓冲 |
| 分析 | ST_Union、ST_Intersection、ST_Simplify | 叠加、合并、简化 |
| 栅格 | ST_Value、ST_Clip、ST_Resample | 栅格取值、裁剪、重采样 |
需求:某零售公司要评估每个商圈 3 公里内有多少家门店。数据是门店表(含 point 几何)和商圈表(含 polygon 几何)。一条 SQL 完成:
SELECT b.name AS 商圈, count(s.id) AS 门店数, round(avg(ST_Distance(s.geom::geography, b.geom::geography))) AS 平均距离米 FROM districts b LEFT JOIN stores s ON ST_DWithin(s.geom::geography, b.geom::geography, 3000) WHERE b.type = '商圈' GROUP BY b.id, b.name;
注意这里把 geometry 转成 geography 做球面距离判断,3 公里范围内结果比平面计算更符合真实道路距离的预期。数据量大时,给 stores 表的 geom 建 GiST 索引,查询从几秒降到几十毫秒。
| 数据库 | 空间支持 | 适合场景 | 局限 |
|---|---|---|---|
| PostgreSQL + PostGIS | 最全 | 中大型 GIS 系统 | 需要单独部署 |
| MySQL Spatial | 基础函数 | 轻量 Web 项目 | 函数少、分析弱 |
| SQL Server Spatial | 较全 | 微软技术栈企业 | 商业授权 |
| Oracle Spatial | 很全 | 大型政企 | 贵、重 |
| SQLite + Spatialite | 基础 | 移动端离线 | 并发弱 |
选型建议:新项目无特殊约束一律 PostGIS;已有 MySQL 的轻量项目可用 MySQL 的空间函数做简单查询,复杂分析还是交给 PostGIS。
空间数据库的查询性能,除了索引还受几个因素影响,这里补充几条经验:
WHERE ST_Intersects(geom, 查询范围) 粗筛,再对少量结果做精确计算,比直接对大表算快得多。WHERE ST_Length(geom) > 10 这类写法无法利用索引;把长度预先存成普通字段并建 B 树索引。-- 批量导入示例(shp2pgsql 管道导入) shp2pgsql -s 4490 -D parcels.shp parcels | psql -d gis_test -- -D 表示用 COPY 模式,速度快
CREATE EXTENSION postgis,GEOMETRY 类型存几何。ST_Distance/ST_Within/ST_Intersects/ST_Buffer/ST_Area。ST_DWithin 配索引快速过滤附近要素。下一节讲空间索引——怎么让空间查询快起来。