几何类型概述
PostgreSQL 内置了一组几何类型(Geometric Type),可以把点、线、矩形、多边形、圆这类平面图形直接存进数据库,并用操作符做距离计算、包含判断和相交检测。它们适合做简单的位置与区域计算;当你需要坐标系转换、球面距离、复杂空间分析时,则应改用 PostGIS 扩展。
前置知识
阅读本节前,建议先了解:自定义范围类型
基础概念
为什么数据库要内置几何类型
很多业务只需要"二维平面上的位置与形状",例如:
- 门店选址:判断某个坐标是否落在配送范围内。
- 仓库布局:记录货架矩形,判断两个货架是否重叠。
- 游戏/仿真:存储点与路径,计算两点距离。
- 图像标注:存标注框(
box)与多边形轮廓。
这些需求不需要地理坐标系,只需要平面几何计算。PostgreSQL 内置几何类型正好覆盖这一层,零扩展、零依赖。
七种内置几何类型
| 类型 | 名称 | 存储大小 | 文本表示示例 | 典型用途 |
|---|---|---|---|---|
point | 点 | 16 字节 | (1,2) | 坐标、位置 |
line | 无限直线 | 32 字节 | {1,-1,0} | 数学直线(Ax+By+C=0) |
lseg | 线段 | 32 字节 | [(1,1),(3,3)] | 有限线段 |
box | 矩形 | 32 字节 | (3,3),(1,1) | 包围盒、标注框 |
path | 路径 | 16+16n 字节 | [(1,1),(2,2),(3,1)] | 折线(开放/闭合) |
polygon | 多边形 | 40+16n 字节 | ((1,1),(2,2),(3,1)) | 区域、围栏 |
circle | 圆 | 24 字节 | <(1,2),3> | 半径范围、服务圈 |
术语提示
几何类型(Geometric Type)、空间索引(Spatial Index)、坐标参考系统(SRID,Spatial Reference System Identifier)、包围盒(Bounding Box)。PostGIS 相关章节会大量出现 SRID。
点(point)
点是所有几何类型中最常用的。它就是一个 (x, y) 坐标对。
SELECT '(1,2)'::point AS p,
point(3, 4) AS p2;
-- 结果:
-- p | p2
-- -------+-------
-- (1,2) | (3,4)线(line)与线段(lseg)
这两个容易混淆:
line是无限延伸的直线,用三个系数{A,B,C}表示方程Ax + By + C = 0。lseg是有限线段,由两个端点确定。
-- 无限直线:{A,B,C}
SELECT '{1,-1,0}'::line AS diag; -- 直线 y = x
-- 有限线段:两个端点
SELECT '[(1,1),(3,3)]'::lseg AS seg;
-- 结果:
-- diag | seg
-- --------+-------------
-- {1,-1,0} | [(1,1),(3,3)]矩形(box)
box 用一对对角点表示,与输入顺序无关:PostgreSQL 会自动把它们规范化为"右上角, 左下角"。
SELECT '(1,1),(3,3)'::box AS b1,
'(3,3),(1,1)'::box AS b2; -- 顺序颠倒,结果相同
-- 结果:
-- b1 | b2
-- -----------+-----------
-- (3,3),(1,1) | (3,3),(1,1)输出格式固定为 (右上角),(左下角),即 (high_x,high_y),(low_x,low_y)。
路径(path)与多边形(polygon)
两者都是点序列,区别在闭合性与语义:
-- 路径:方括号,可以是开放折线,也可以首尾点重复表示闭合
SELECT '[(1,1),(2,2),(3,1)]'::path AS open_path,
'((1,1),(2,2),(3,1))'::path AS closed_path;
-- 多边形:圆括号,隐式闭合,不存储重复的首尾点
SELECT '((1,1),(2,2),(3,1))'::polygon AS poly;| 特性 | path | polygon |
|---|---|---|
| 定界符 | [...] 开放 / ((...)) 闭合 | ((...)) |
| 是否可自交 | 可以 | 不建议(面积计算会异常) |
| 面积计算 | 仅闭合路径有意义 | area() 直接支持 |
| 典型用途 | 行车轨迹、折线 | 区域围栏、地块 |
圆(circle)
用 <(圆心),半径> 表示:
SELECT '<(1,2),3>'::circle AS c,
circle(point(1,2), 3) AS c2;输入格式的容错性
PostgreSQL 的几何类型输入解析非常宽松,以下几种写法等价:
SELECT '(1,2)'::point AS a,
'1,2'::point AS b, -- 省略外层括号
' ( 1 , 2 ) '::point AS c; -- 允许空白语法与用法
字面量写法速查
-- 点
'(x,y)'
-- 直线(系数 A,B,C)
'{A,B,C}'
-- 线段(两端点)
'[(x1,y1),(x2,y2)]'
-- 矩形(任一对角点,顺序不限)
'((x1,y1),(x2,y2))'
-- 路径(方括号开放、双圆括号闭合)
'[(x1,y1),(x2,y2),...]'
'((x1,y1),(x2,y2),...)'
-- 多边形
'((x1,y1),(x2,y2),...)'
-- 圆
'<(x,y),r>'构造函数
每种类型都有对应构造函数,便于用表达式动态拼装:
| 构造函数 | 说明 |
|---|---|
point(x, y) | 由两个浮点数构造点 |
line(point, point) | 由两点确定直线 |
lseg(point, point) | 由两点构造线段 |
box(point, point) | 由两对角点构造矩形 |
path(polygon) | 由多边形构造闭合路径 |
polygon(n, circle) | 由圆构造 n 边正多边形 |
circle(point, r) | 由圆心与半径构造圆 |
-- 用构造函数而非字面量,值可以来自列或变量
SELECT box(point(0, 0), point(10, 5)) AS rect,
circle(point(5, 2.5), 2) AS dot;建表使用几何类型
DROP TABLE IF EXISTS stores;
CREATE TABLE stores (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name TEXT NOT NULL,
location POINT NOT NULL, -- 门店坐标
coverage CIRCLE NOT NULL, -- 配送半径
bbox BOX NOT NULL -- 库区包围盒
);
INSERT INTO stores (name, location, coverage, bbox) VALUES
('城东店', '(116.42,39.92)', '<(116.42,39.92),3>', '(116.45,39.95),(116.39,39.89)'),
('城西店', '(116.30,39.92)', '<(116.30,39.92),5>', '(116.35,39.96),(116.25,39.88)');
SELECT id, name, location, coverage FROM stores ORDER BY id;
-- 结果:
-- id | name | location | coverage
-- ----+--------+--------------+-------------------
-- 1 | 城东店 | (116.42,39.92) | <(116.42,39.92),3>
-- 2 | 城西店 | (116.3,39.92) | <(116.3,39.92),5>详细说明
几何类型 vs 两个数值列
很多团队用 lat DOUBLE PRECISION, lng DOUBLE PRECISION 存坐标。对比一下:
| 维度 | point | 两个 double precision |
|---|---|---|
| 距离计算 | <-> 操作符一行搞定 | 需手写平方和开方 |
| 是否在区域内 | @> / <@ 原生支持 | 需手写几何算法 |
| 空间索引 | 原生 GiST / SP-GiST | 需表达式索引 |
| 数据完整性 | 坐标天然成对 | 可能只填一半 |
| 输出 | (x,y) 紧凑 | 两列 |
| 可读性 | 需了解语法 | 直观 |
结论:只存不查时两列更简单;要按位置做查询时 point 明显更优。
距离计算:<-> 操作符
<-> 是所有几何类型通用的距离操作符,返回 double precision:
SELECT '(0,0)'::point <-> '(3,4)'::point AS d_pp, -- 点到点:5
'(0,0)'::point <-> '[(0,0),(0,10)]'::lseg AS d_pl; -- 点在线上:0 d_pp | d_pl
------+------
5 | 0包含与相交:@>、<@、&&
三个最常用的"空间关系"操作符:
| 操作符 | 含义 | 示例 |
|---|---|---|
@> | 左包含右 | circle @> point:点在圆内 |
<@ | 左被右包含 | point <@ circle:同上 |
&& | 包围盒有重叠 | box && box:两矩形可能相交 |
-- 点是否在圆内(即是否在配送范围内)
SELECT '<(0,0),5>'::circle @> '(3,4)'::point AS inside_1,
'<(0,0),5>'::circle @> '(3,5)'::point AS inside_2;
-- 结果:
-- inside_1 | inside_2
-- ----------+----------
-- t | f&& 只比较包围盒,是快速预筛,不一定代表真实相交,注意别误用。
与 PostGIS 的定位区分
这是初学者最容易踩的坑:内置几何类型不是 GIS 类型。
| 能力 | 内置几何类型 | PostGIS geometry / geography |
|---|---|---|
| 坐标系 | 无,纯平面 | 支持 SRID、投影转换 |
| 距离单位 | 坐标单位(抽象) | 米 / 度,可跨投影计算 |
| 球面计算 | 不支持 | geography 支持 |
| 函数丰富度 | 二十余个 | 数百个(ST_*) |
| 格式支持 | 仅自身文本格式 | WKT / WKB / GeoJSON / KML |
| 安装 | 内置 | 需 CREATE EXTENSION postgis |
| 精度 | double precision | 支持更多几何模型(含 Z/M 维度) |
选择口诀:
- 纯平面、小型数据集、简单判断 → 内置几何类型。
- 经纬度、真实距离(公里)、坐标系、复杂分析 → PostGIS。
更详细的 PostGIS 介绍见后续章节。
索引支持
内置几何类型支持 GiST 与 SP-GiST 索引,对 &&、@>、<@、<->(KNN)等有用。
-- GiST:通用,支持 KNN 距离排序
CREATE INDEX idx_stores_location ON stores USING GIST (location);
-- 按距离排序时会走 GiST 的 KNN 能力
SELECT name, location <-> '(116.40,39.90)'::point AS dist
FROM stores
ORDER BY location <-> '(116.40,39.90)'::point
LIMIT 1;注意
box 类型在 PostgreSQL 12 之前支持 B-tree 索引,之后已移除;如遇老文档提到"box 可建 btree",属于过时信息。需要按矩形做索引时用 GiST。
常见几何类型的可比较性
并非所有几何类型都支持全部比较操作符:
| 类型 | = | < > 排序 | 可建 B-tree |
|---|---|---|---|
point | ✔(PG 9.4+ 起按坐标比较) | ✘ | 需 btree_gist 或用表达式 |
box | ✔ | ✔(按面积) | ✘ |
circle | ✔ | ✔(按面积) | ✘ |
polygon | ✔ | ✔(按面积) | ✘ |
path | ✔ | ✔(按几何比较) | ✘ |
lseg / line | ✔ | ✔ | ✘ |
point 的排序缺失是历史遗留:(1,2) 与 (2,1) 谁大谁小在几何上没有自然答案,所以 PostgreSQL 不提供 <。
实战示例
用"门店覆盖范围查询"串起几何类型的常用能力。
第 1 步:准备数据
DROP TABLE IF EXISTS stores;
CREATE TABLE stores (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name TEXT NOT NULL,
location POINT NOT NULL,
coverage CIRCLE NOT NULL
);
INSERT INTO stores (name, location, coverage) VALUES
('城东店', '(10,10)', '<(10,10),3>'),
('城西店', '(0,0)', '<(0,0),5>'),
('城南店', '(5,-5)', '<(5,-5),4>');
SELECT id, name, location, coverage FROM stores ORDER BY id; id | name | location | coverage
----+--------+----------+-------------------
1 | 城东店 | (10,10) | <(10,10),3>
2 | 城西店 | (0,0) | <(0,0),5>
3 | 城南店 | (5,-5) | <(5,-5),4>第 2 步:某坐标落在哪些门店的覆盖范围内
SELECT name, '<(0,0),5>'::circle @> location AS wrong_way, -- 方向反了,恒 false
coverage @> '(2,2)'::point AS covers
FROM stores
ORDER BY id; name | wrong_way | covers
--------+-----------+--------
城东店 | f | f
城西店 | f | t
城南店 | f | f第一条 <(0,0),5> @> location 判断的是"圆心在原点的圆是否包含门店坐标",而不是"门店的圆是否包含该点",注意操作数方向。
第 3 步:按距离排序找最近门店
SELECT name,
location <-> '(1,1)'::point AS distance
FROM stores
ORDER BY location <-> '(1,1)'::point
LIMIT 2; name | distance
--------+----------
城西店 | 1.4142135623730951
城南店 | 7.211102550927978第 4 步:半径是否覆盖某点(两圆是否相交)
-- 两圆相交:圆心距 <= 半径之和
SELECT '<(10,10),3>'::circle && '<(0,0),5>'::circle AS bbox_overlap,
'<(10,10),3>'::circle <-> '<(0,0),5>'::circle AS center_dist; bbox_overlap | center_dist
--------------+-------------
f | 14.142135623730951圆心距约 14.14,远大于半径和 8,因此不重叠。
第 5 步:用矩形做包围盒预筛
-- 先用包围盒快速缩小范围,再精确判断
SELECT name FROM stores
WHERE box(location <-> '(-100,-100)'::point, location) && box('(0,0)'::point, '(12,12)'::point)
ORDER BY id;注意
上面这个 box(location <-> ..., location) 写法是为了演示操作符组合,实际业务中应直接用 location @> ... 或 && 配合 box 列。用表达式构造 box 无法走索引。
第 6 步:清理
DROP TABLE IF EXISTS stores;注意事项
- 内置几何类型没有坐标系概念。 存经纬度后用
<->得到的"距离"是度,不是米,且高纬度会严重失真。 &&比较的是包围盒。 返回true只代表外框重叠,不代表真实相交,需配合@>等精判。box输出顺序固定。 始终是(右上角),(左下角),输入顺序不影响存储。point不支持<排序。 想按坐标排序需拆成(p)[0]/(p)[1]或转成数组。point的=在 PostgreSQL 9.4 之前是按位比较,之后改为按坐标比较,跨版本迁移时注意。polygon自交会导致面积异常。 四边形"蝴蝶形"输入不报错,但area()结果是两个三角形之差。- 几何类型没有精度控制。 全部是
double precision,不适合金额类精确计算。 line很少用于业务。 它表示无限直线,实际需求几乎都是lseg。- 内置几何类型的函数只有二十余个。 一旦发现需要
ST_*系列函数,说明该上 PostGIS 了。 - 索引是 GiST / SP-GiST,不是 B-tree。 建错索引类型会导致查询走全表扫描。
circle的比较是按面积。 两个不同形状的几何体可能"相等"(面积相同),这种比较语义在业务中通常没意义。- 几何字面量里不要有中文标点。 中文全角括号会导致解析错误,且报错信息不够直观。
最佳实践
- 先明确是否需要地理功能。 涉及经纬度、公里数、坐标系,直接上 PostGIS,别用内置类型硬扛。
point列名用location/position这类语义化名称。 不要叫p或geom,后者与 PostGIS 冲突。- 热查询列一定建 GiST 索引。 尤其是
<->距离排序,GiST 的 KNN 能避免全表排序。 - 用
&&做预筛、用@>/<@做精判。 这是空间查询的标准两段式写法。 box适合做包围盒缓存列。 把复杂几何体的外框单独存一列,按外框查询再精判,可显著提速。- 距离计算前后统一单位。 内置类型是抽象坐标单位,务必在文档里写清楚"1 单位 = 1 米"这类约定。
- 别用几何类型存真实的经纬度。 度与米不可混算,这是最常见的生产事故来源。
- 写测试校验几何语义。 例如断言"点在圆边界上"的
@>结果,明确边界是包含的。 - 数据量超过百万行且需空间分析时迁移到 PostGIS。 迁移成本远低于持续维护自造几何算法。
下一节
继续学习:几何操作符与函数
参考链接
- PostgreSQL 官方文档:Geometric Types
- PostgreSQL 官方文档:Geometric Functions and Operators
- PostgreSQL 官方文档:GiST Indexes
- PostgreSQL 官方文档:Index Types
- PostgreSQL 官方文档:PostGIS