丰富数据类型 · 类型即一等公民
PostgreSQL 是 SQL 数据库里类型最齐全的之一:UUID、INET/CIDR、INTERVAL、NUMERIC、BYTEA 全是内置的一等公民,能索引、能比较、能加约束。别的库要在应用层模拟的东西,这里开箱即用。
丰富数据类型 · 类型即一等公民
如果只能用一句话概括 PostgreSQL 和别家的差别,我会说:在 PG 里,「类型」是可以无限扩展的一等公民,而不是几个内置基本类型的固定清单。
一个 IP 地址、一段时间间隔、一个 128 位的唯一 ID、一段任意精度的金额——在很多数据库里,这些要么塞进 VARCHAR 靠应用层解释,要么用 DECIMAL 勉强凑。而在 PostgreSQL 里,它们各自是真正的类型:有专属的存储格式、专属的运算符、能建索引、能进约束。
一行数据,把「类型齐全」摊开看
我们的实测里,先插入了一行「什么类型都有」的数据,再取回来:
取回: id=e4ec036b-3c97-4fe1-8d91-4e735b7607bc
tags=['admin', 'ops']
meta={'role': 'lead', 'perms': ['read', 'write']}
ip=192.168.1.0/24
life=2 days, 3:00:00
price=3.1416
blob_len=3这一行里藏着六种「别处不好搞」的类型:UUID、TEXT[] 数组、JSONB、CIDR 网段、INTERVAL 时间间隔、NUMERIC 精确小数。逐个看。
UUID:16 字节,不是 36 个字符
UUID 是 128 位的通用唯一识别码。关键在存储:PostgreSQL 把它存成紧凑的 16 字节二进制,而不是那串带连字符的 36 个字符。
SELECT gen_random_uuid();
-- e4ec036b-3c97-4fe1-8d91-4e735b7607bcgen_random_uuid()从 PG 13 起是内置函数(更早要靠pgcrypto/uuid-ossp扩展)。- 对比 MySQL:8.0 之前没有 UUID 类型,通常存成
CHAR(36)(占 36 字节 + 排序差),或BINARY(16)再自己写函数转换。PostgreSQL 一步到位。
UUID 做主键要小心一个性能坑:完全随机的 UUID(v4)会让 B-tree 主键索引的插入点四处乱跳,页面频繁分裂、缓存命中率差。想用 UUID 又想要顺序性,可考虑 UUIDv7(时间有序,PG 18 已可用相关扩展/函数生成),兼得唯一性与「近似自增」的插入局部性。
INET / CIDR:IP 是类型,不是字符串
把 IP 存成字符串,你就没法问「这个地址在不在那个网段里」——只能取回来在应用层解析。PostgreSQL 的 inet / cidr 类型自带网络语义:
SELECT '192.168.1.50'::inet <<= '192.168.1.0/24'::cidr; -- 子网包含
-- 结果: true(192.168.1.50 ∈ 192.168.1.0/24 ✓)<<=、>>=、&& 这些运算符直接做子网包含 / 被包含 / 重叠判断,还能用 GiST 索引加速。做 IP 白名单、地理封锁、访问审计,这类型省掉一大坨解析代码。MySQL 只有 INET_ATON/INET6_ATON 把 IP 转成整数,做不到「网段」这一层的语义。
INTERVAL:日历感知的时间算术
INTERVAL 存的是「一段时长」,而且分开记月、日、微秒——因为「1 个月」不是固定天数,加到不同日期上结果不同。这叫日历感知(calendar-aware):
SELECT now() + '2 days 3 hours'::interval;
-- now() 之上加 2 天 3 小时 → 2026-06-27 07:05:00+00时间戳直接和间隔做加减,得到新的时间戳。算「7 天后过期」「每月账单日」这种,一个运算符搞定。MySQL 靠 DATE_ADD(now(), INTERVAL 2 DAY) 也能做,但 INTERVAL 在 PG 里是能存进列、能参与比较的值,表达力更强。
NUMERIC:算钱绝不用浮点
这是最该刻进 DNA 的一条:金额永远别用 FLOAT / DOUBLE。浮点是二进制近似,0.1 + 0.2 ≠ 0.3 这种误差在财务上是灾难。NUMERIC(= DECIMAL)是任意精度的十进制,精确到你指定的小数位:
1/3 - 0.333...(24 个 3) = -3.333E-21 -- NUMERIC 连这点尾差都如实保留,不含糊- 实现上,
NUMERIC按万进制(base-10000)的数字组存储,是精确十进制运算,不是浮点近似。 - 代价:比定长的
int/float8慢、占空间多。所以它用在「对就是对、错一分都不行」的地方——金额、汇率、计量。 - 这一条几乎所有正经数据库都支持(
DECIMAL),但常有人图快用了浮点——PostgreSQL 也不例外地提醒你:该用NUMERIC就用。
BYTEA:任意字节流
BYTEA 存任意二进制:小图标、哈希值、加密后的密文、protobuf 序列化结果。上面那行里 blob_len=3 就是存了 3 个字节。需要存大对象时它配合 TOAST 机制自动把超长值挪到附属存储(见 索引一章 会再提到 TOAST),对你透明。
还有一批,后面专章讲
丰富类型里最能打的几个,值得各开一章:
- 数组(
int[]/text[]/jsonb[])—— 标签、权限位、多选枚举,免掉多对多映射表。见 数组运算。 - JSONB —— 把 PG 从「关系库」扩展成「关系 + 文档」混合库。见 JSONB。
- 范围类型(
int4range/tsrange)+ 生成列 —— 会议室不许时段重叠这种约束,数据库层面强制。见 范围类型与生成列。
横向对比
| 类型 | PostgreSQL | MySQL | Oracle | SQL Server |
|---|---|---|---|---|
| UUID | uuid 内置(16 字节) | 无类型,BINARY(16) 模拟 | RAW(16) / SYS_GUID() | uniqueidentifier |
| IP 地址 | inet / cidr + 网段运算 | 仅 INET_ATON 转整数 | 无原生类型 | 无原生类型 |
| 时间间隔 | interval 是值类型 | 仅 INTERVAL 表达式 | INTERVAL DAY TO SECOND | 无独立类型 |
| 精确小数 | numeric | DECIMAL | NUMBER | decimal |
| 数组 | 一等公民 | 无(用 JSON 模拟) | VARRAY / 嵌套表(笨重) | 无 |
| 二进制 | bytea / 大对象 | BLOB | BLOB / RAW | varbinary |
一句话总结:别家有的类型 PG 都有,PG 有的类型别家常常没有。 而且 PostgreSQL 的类型系统是可扩展的——扩展(如 PostGIS 的 geometry)能注册全新类型,和内置类型一样能索引、能运算。
应用场景
- 用对类型省掉一层代码:IP 白名单用
inet、时长用interval、金额用numeric,把「解析 + 校验」从应用层下沉到数据库,少写也少错。 - 主键选型:分布式生成 ID 用
uuid(注意 v4 的插入局部性问题,规模大时考虑 v7)。 - 审计与计量:精确金额、精确时长,
numeric+interval是标配。
小结与下一章
PostgreSQL 的「类型即一等公民」不是花哨——它把大量本该在应用层反复解析、校验、易错的逻辑,变成数据库内置的、能索引能约束的能力。这是它区别于其它库的第一层护城河。
类型里最能改变你建模方式的,是数组——它让「多对多」这种经典关系建模,在很多场景下直接省掉一张中间表。下一章就看它:👉 数组运算:把「多对多」压进一列。
参考文献
- [1] PostgreSQL 官方文档.「Chapter 8. Data Types.」https://www.postgresql.org/docs/current/datatype.html —— 全部内置类型总览。
- [2] PostgreSQL 官方文档.「8.9. Network Address Types.」https://www.postgresql.org/docs/current/datatype-net-types.html ——
inet/cidr与网段运算符。 - [3] PostgreSQL 官方文档.「8.1.2. Arbitrary Precision Numbers.」https://www.postgresql.org/docs/current/datatype-numeric.html ——
numeric的精度与实现说明。 - [4] PostgreSQL 官方文档.「9.14. UUID Functions / gen_random_uuid.」https://www.postgresql.org/docs/current/functions-uuid.html —— PG 13+ 内置 UUID 生成。