文章目录
-
- 一、基本原则:选择数据类型的通用准则
-
- [1.1 精确性优先](#1.1 精确性优先)
- [1.2 最小化存储](#1.2 最小化存储)
- [1.3 语义清晰](#1.3 语义清晰)
- [1.4 考虑未来扩展](#1.4 考虑未来扩展)
- [1.5 利用约束保障完整性](#1.5 利用约束保障完整性)
- 二、数值类型:精度、范围与性能的权衡
-
- [2.1 整数类型](#2.1 整数类型)
- [2.2 浮点类型](#2.2 浮点类型)
- [2.3 精确数值:NUMERIC / DECIMAL](#2.3 精确数值:NUMERIC / DECIMAL)
- [三、字符类型:TEXT、VARCHAR 与 CHAR 的真相](#三、字符类型:TEXT、VARCHAR 与 CHAR 的真相)
-
- [3.1 三种类型对比](#3.1 三种类型对比)
- [3.2 长文本与大对象](#3.2 长文本与大对象)
- [四、时间类型:DATE、TIME、TIMESTAMP 与 TIMESTAMPTZ](#四、时间类型:DATE、TIME、TIMESTAMP 与 TIMESTAMPTZ)
-
- [4.1 核心类型对比](#4.1 核心类型对比)
- [4.2 间隔类型:INTERVAL](#4.2 间隔类型:INTERVAL)
- 五、布尔与枚举类型:提升语义清晰度
-
- [5.1 BOOLEAN](#5.1 BOOLEAN)
- [5.2 ENUM(枚举)](#5.2 ENUM(枚举))
- 六、网络与硬件地址类型:INET、CIDR、MACADDR
- [七、JSON 与 JSONB:半结构化数据的终极武器](#七、JSON 与 JSONB:半结构化数据的终极武器)
- 八、几何与地理空间类型
-
- [8.1 内置几何类型](#8.1 内置几何类型)
- [8.2 PostGIS 扩展(生产推荐)](#8.2 PostGIS 扩展(生产推荐))
- [九、全文搜索类型:TSVECTOR 与 TSQUERY](#九、全文搜索类型:TSVECTOR 与 TSQUERY)
- [十、范围类型(Range Types):处理区间数据](#十、范围类型(Range Types):处理区间数据)
-
- [10.1 内置范围类型](#10.1 内置范围类型)
- [10.2 核心操作](#10.2 核心操作)
- 十一、自定义类型:复合类型与域(Domain)
-
- [11.1 复合类型(Composite Type)](#11.1 复合类型(Composite Type))
- [11.2 域(Domain)](#11.2 域(Domain))
- 十二、避坑:常见错误与反模式
-
- [12.1 用字符串存数字或日期](#12.1 用字符串存数字或日期)
- [12.2 过度使用 UUID 作主键](#12.2 过度使用 UUID 作主键)
- [12.3 忽略 NULL 语义](#12.3 忽略 NULL 语义)
- [12.4 滥用 JSONB 替代关系模型](#12.4 滥用 JSONB 替代关系模型)
本文将系统性地剖析 PostgreSQL 中各类数据类型的特性、适用场景、潜在陷阱及最佳实践,覆盖数值、字符、时间、布尔、枚举、网络、JSON、几何、全文搜索、范围、自定义类型等核心类别,并结合真实案例说明选型逻辑。
一、基本原则:选择数据类型的通用准则
在关系型数据库设计中,数据类型的选取看似基础,实则深刻影响着系统的存储效率、查询性能、数据完整性、扩展能力乃至长期维护成本。PostgreSQL 作为功能最丰富的开源数据库之一,提供了远超传统 SQL 标准的多样化数据类型------从精确的数值类型、灵活的时间处理,到强大的 JSONB、地理空间、全文搜索、自定义复合类型等。然而,"多"并不等于"易用",错误的类型选择往往导致隐性性能瓶颈、存储浪费或逻辑错误。在深入具体类型前,需明确以下通用原则:
1.1 精确性优先
- 存储货币、科学计算等场景,必须使用精确类型 (如
NUMERIC),避免浮点误差。 - 示例:
0.1 + 0.2 = 0.30000000000000004(浮点问题)。
1.2 最小化存储
- 在满足业务需求前提下,选择占用空间最小的类型。
- 更小的行尺寸 → 更高的缓存命中率 → 更快的 I/O 和排序性能。
1.3 语义清晰
- 类型应准确表达数据含义。例如:
- 用
DATE而非TEXT存储日期; - 用
INET而非VARCHAR存储 IP 地址。
- 用
1.4 考虑未来扩展
- 避免过早优化导致后续修改困难。例如:
- 用户 ID 初始为
INT,但业务增长后需支持分布式 ID(如 Snowflake),应预留为BIGINT。
- 用户 ID 初始为
1.5 利用约束保障完整性
-
即使类型本身不限制范围,也应通过
CHECK约束或域(Domain)强化业务规则。sqlCREATE DOMAIN email AS TEXT CHECK (VALUE ~ '^[^@]+@[^@]+\.[^@]+$');
终极心法 : "用最精确、最紧凑、最语义化的类型表达你的数据。"
数据库不仅是存储引擎,更是业务逻辑的载体。正确的类型选择,是构建健壮、高效、可维护系统的第一步。
二、数值类型:精度、范围与性能的权衡
PostgreSQL 提供多种数值类型,核心区别在于精度、范围、存储大小及是否为精确计算。
2.1 整数类型
| 类型 | 范围 | 存储 | 适用场景 |
|---|---|---|---|
SMALLINT |
-32768 ~ +32767 | 2 字节 | 枚举状态码、小计数器 |
INTEGER |
-2147483648 ~ +2147483647 | 4 字节 | 主键、外键、常规计数(默认选择) |
BIGINT |
±9223372036854775807 | 8 字节 | 大流量 ID(如订单号)、分布式系统 |
选型建议:
- 主键/外键 :除非确定数据量极小(< 3 万),否则优先用
INTEGER;若预计超 20 亿行,直接用BIGINT。 - 避免过度节省 :
SMALLINT仅比INTEGER节省 2 字节,但溢出风险高,现代系统内存充足,通常不值得冒险。
2.2 浮点类型
| 类型 | 精度 | 存储 | 特性 |
|---|---|---|---|
REAL |
6 位十进制 | 4 字节 | IEEE 754 单精度 |
DOUBLE PRECISION |
15 位十进制 | 8 字节 | IEEE 754 双精度 |
适用场景:
- 科学计算、传感器数据、图形坐标等允许近似值的场景。
- 绝不用于:货币、财务、需要精确比较的字段。
2.3 精确数值:NUMERIC / DECIMAL
- 语法:
NUMERIC(precision, scale),如NUMERIC(10,2)表示共 10 位,小数占 2 位。 - 存储:可变长度,按需分配。
- 优势:无舍入误差,完全精确。
- 代价:计算速度慢于整数和浮点。
典型应用:
- 货币金额:
NUMERIC(19,4)(支持万亿级金额,4 位小数用于汇率计算) - 百分比:
NUMERIC(5,2)(如 99.99%)
⚠️ 注意:
NUMERIC无默认精度,若省略(p,s),则可存储任意精度值(但性能更差)。
三、字符类型:TEXT、VARCHAR 与 CHAR 的真相
PostgreSQL 对字符类型的处理与其他数据库有显著差异。
3.1 三种类型对比
| 类型 | 含义 | 存储 | 性能 | 建议 |
|---|---|---|---|---|
TEXT |
无长度限制 | 可变 | 最优 | 首选 |
VARCHAR(n) |
最大 n 字符 | 可变 | 略低于 TEXT | 需强制长度限制时 |
CHAR(n) |
固定 n 字符,不足补空格 | 固定 | 最差 | 避免使用 |
关键事实:
- 在 PostgreSQL 中,
TEXT、VARCHAR、CHAR底层存储完全相同(均使用 varlena 结构)。 VARCHAR(n)的长度检查会带来轻微 CPU 开销。CHAR(n)会自动填充空格 ,导致比较时需TRIM(),极易引发逻辑错误。
结论:
- 99% 场景使用
TEXT; - 仅当业务强要求最大长度(如身份证号 18 位),用
VARCHAR(18)并配合CHECK约束; - 永远不要用
CHAR(n)。
3.2 长文本与大对象
-
普通
TEXT可存储至 1GB(受 TOAST 机制支持)。 -
超大文件(如视频、PDF)应使用 Large Objects (LOB) 或外部存储 (数据库仅存路径)。
sql-- 创建大对象 SELECT lo_create(0); -- 返回 OID
四、时间类型:DATE、TIME、TIMESTAMP 与 TIMESTAMPTZ
时间处理是数据库常见痛点,PostgreSQL 提供了清晰的类型划分。
4.1 核心类型对比
| 类型 | 含义 | 时区 | 存储 | 推荐 |
|---|---|---|---|---|
DATE |
日期(年月日) | 无 | 4 字节 | 日历事件 |
TIME |
时间(时分秒) | 无 | 8 字节 | 营业时间 |
TIMESTAMP |
日期+时间 | 无 | 8 字节 | 避免使用 |
TIMESTAMPTZ |
日期+时间+时区 | 有 | 8 字节 | 绝对首选 |
关键区别:
TIMESTAMP:不带时区 ,存储字面值。例如'2025-01-01 12:00:00'在任何时区都显示相同。TIMESTAMPTZ:带时区,存储为 UTC,显示时自动转换为客户端时区。
示例:
sql
SET timezone = 'Asia/Shanghai';
INSERT INTO logs(ts) VALUES ('2025-01-01 12:00:00'); -- 存为 UTC 04:00
SET timezone = 'UTC';
SELECT ts FROM logs; -- 显示 2025-01-01 04:00:00+00
最佳实践:
-
所有时间戳字段必须使用
TIMESTAMPTZ; -
应用层统一以 UTC 交互,前端负责时区转换;
-
避免在
WHERE中对时间字段使用函数(破坏索引),改用范围查询:sql-- 好 WHERE created_at >= '2025-01-01' AND created_at < '2025-02-01' -- 坏 WHERE date_trunc('month', created_at) = '2025-01-01'
4.2 间隔类型:INTERVAL
-
表示时间跨度,如
'1 day 2 hours'。 -
适用于计算、有效期等场景:
sqlSELECT now() + INTERVAL '30 days'; -- 30 天后
五、布尔与枚举类型:提升语义清晰度
5.1 BOOLEAN
-
存储:1 字节
-
值:
TRUE、FALSE、NULL -
优于 :用
INT(0/1)或CHAR('Y'/'N')表示布尔状态。 -
查询简洁:
sqlSELECT * FROM users WHERE is_active;
5.2 ENUM(枚举)
-
定义有限集合的字符串值,如状态码。
-
创建:
sqlCREATE TYPE order_status AS ENUM ('pending', 'shipped', 'delivered', 'cancelled'); CREATE TABLE orders (id SERIAL, status order_status); -
优势 :
- 存储高效(内部用整数编码)
- 自动校验值合法性
- 比
VARCHAR节省空间
-
劣势 :
- 修改枚举值需
ALTER TYPE ... ADD VALUE(PostgreSQL 10+ 支持) - 不支持跨数据库移植
- 修改枚举值需
替代方案 :若需频繁变更或国际化,可用参照表(lookup table)代替。
六、网络与硬件地址类型:INET、CIDR、MACADDR
PostgreSQL 原生支持网络数据类型,避免字符串存储的弊端。
| 类型 | 示例 | 用途 |
|---|---|---|
INET |
'192.168.1.1', '2001:db8::1' |
IP 地址(含子网掩码) |
CIDR |
'192.168.1.0/24' |
网络地址块 |
MACADDR |
'08:00:2b:01:02:03' |
MAC 地址 |
优势:
-
内置验证(非法 IP 无法插入)
-
支持网络运算:
sqlSELECT '192.168.1.10'::inet << '192.168.1.0/24'; -- true(属于该网段) -
索引优化(BRIN 索引适合 IP 范围查询)
应用场景:访问日志、防火墙规则、设备管理。
七、JSON 与 JSONB:半结构化数据的终极武器
详见前文《JSONB 详解》,此处强调选型要点:
JSONBvsJSON:除非需保留原始格式(如审计),否则一律用JSONB。- 何时使用 :
- 结构高度动态(如用户配置、API payload)
- 读多写少,且不需频繁 JOIN
- 作为关系模型的补充,而非替代
- 何时避免 :
- 核心业务实体(如用户、订单)应拆分为关系表
- 需要强约束、外键、复杂事务
示例:
sql
-- 好:用户偏好设置
CREATE TABLE users (id SERIAL, name TEXT, prefs JSONB);
-- 坏:将订单明细存为 JSON
-- 应拆分为 orders + order_items 两张表
八、几何与地理空间类型
8.1 内置几何类型
-
POINT、LINE、LSEG、BOX、PATH、POLYGON、CIRCLE -
适用于简单图形计算(如地图标注、碰撞检测)
-
示例:
sqlCREATE TABLE locations (name TEXT, coord POINT); SELECT name FROM locations WHERE coord <@ BOX '((0,0),(10,10))';
8.2 PostGIS 扩展(生产推荐)
-
安装
postgis扩展后,提供GEOMETRY、GEOGRAPHY类型 -
支持 WGS84 坐标系、距离计算、空间索引(GiST)
-
必须用于 :LBS、物流、地理围栏等场景
sqlCREATE EXTENSION postgis; CREATE TABLE places (name TEXT, geom GEOMETRY(POINT, 4326));
九、全文搜索类型:TSVECTOR 与 TSQUERY
PostgreSQL 内置全文检索能力,无需外部搜索引擎。
TSVECTOR:文档的词位向量(已分词、去停用词、标准化)TSQUERY:搜索条件表达式
工作流程:
sql
-- 创建向量
UPDATE articles SET tsv = to_tsvector('english', title || ' ' || body);
-- 创建 GIN 索引
CREATE INDEX idx_tsv ON articles USING GIN(tsv);
-- 搜索
SELECT * FROM articles WHERE tsv @@ to_tsquery('english', 'database & performance');
优势:
- 高性能(GIN 索引)
- 支持权重、高亮、相关性排序
- 适合中小型全文检索需求
十、范围类型(Range Types):处理区间数据
PostgreSQL 独创的范围类型,优雅解决"时间段"、"价格区间"等问题。
10.1 内置范围类型
| 类型 | 示例 |
|---|---|
int4range |
[10,20) |
numrange |
(1.5, 5.5] |
tsrange |
['2025-01-01', '2025-12-31') |
tstzrange |
带时区的时间范围 |
10.2 核心操作
-
重叠检查 :
sqlSELECT int4range(10, 20) && int4range(15, 25); -- true -
约束排他 (防止重叠):
sqlCREATE TABLE room_bookings ( room TEXT, during TSRANGE, EXCLUDE USING GIST (room WITH =, during WITH &&) );此约束确保同一房间的预订时间不重叠。
应用场景:日历预约、价格策略、资源调度。
十一、自定义类型:复合类型与域(Domain)
11.1 复合类型(Composite Type)
-
类似 C 结构体,组合多个字段。
-
创建:
sqlCREATE TYPE address AS (street TEXT, city TEXT, zip TEXT); CREATE TABLE users (id SERIAL, home address); -
访问:
sqlSELECT (home).city FROM users;
适用场景:逻辑上紧密关联的属性组(如地址、坐标)。
11.2 域(Domain)
-
基于现有类型 + 约束,创建语义化新类型。
-
示例:
sqlCREATE DOMAIN us_postal_code AS TEXT CHECK (VALUE ~ '^\d{5}$'); CREATE TABLE addresses (zip us_postal_code);
优势:复用约束逻辑,提升代码可读性。
十二、避坑:常见错误与反模式
12.1 用字符串存数字或日期
- 问题:无法校验、排序错误、计算困难。
- 修复 :用
NUMERIC、DATE等专用类型。
12.2 过度使用 UUID 作主键
- 问题:16 字节 vs 4 字节(INT),索引更大,写入更慢(随机 IO)。
- 建议 :
- 内部系统用
BIGSERIAL; - 对外暴露 ID 用 UUID,但主键仍为整数。
- 内部系统用
12.3 忽略 NULL 语义
- 问题 :
NULL = NULL返回NULL(非 true),导致逻辑错误。 - 对策 :
- 明确字段是否允许 NULL;
- 使用
IS NULL/IS NOT NULL判断; - 考虑用默认值替代 NULL(如
0、'')。
12.4 滥用 JSONB 替代关系模型
- 问题:丧失 ACID、JOIN、约束等关系优势。
- 原则:核心实体关系化,边缘属性文档化。
最后总结:数据类型选型决策树
-
数值:
- 精确计算 →
NUMERIC - 整数 ID →
BIGINT(防溢出) - 浮点 →
DOUBLE PRECISION(仅限科学计算)
- 精确计算 →
-
字符:
- 默认 →
TEXT - 强长度限制 →
VARCHAR(n) - 避免 →
CHAR(n)
- 默认 →
-
时间:
- 绝对时间戳 →
TIMESTAMPTZ - 日期 →
DATE - 时间段 →
TSTZRANGE
- 绝对时间戳 →
-
状态/分类:
- 固定选项 →
ENUM - 动态选项 → 参照表
- 固定选项 →
-
半结构化:
- 动态属性 →
JSONB - 全文检索 →
TSVECTOR
- 动态属性 →
-
特殊领域:
- IP →
INET - 地理 →
PostGIS - 区间 →
RANGE
- IP →