丰富数据类型 · 类型即一等公民

PostgreSQL 是 SQL 数据库里类型最齐全的之一:UUID、INET/CIDR、INTERVAL、NUMERIC、BYTEA 全是内置的一等公民,能索引、能比较、能加约束。别的库要在应用层模拟的东西,这里开箱即用。

作者 David更新于 第 1 篇(共 17 篇)

丰富数据类型 · 类型即一等公民

如果只能用一句话概括 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-4e735b7607bc
  • gen_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)+ 生成列 —— 会议室不许时段重叠这种约束,数据库层面强制。见 范围类型与生成列。

横向对比

类型PostgreSQLMySQLOracleSQL Server
UUIDuuid 内置(16 字节)无类型,BINARY(16) 模拟RAW(16) / SYS_GUID()uniqueidentifier
IP 地址inet / cidr + 网段运算仅 INET_ATON 转整数无原生类型无原生类型
时间间隔interval 是值类型仅 INTERVAL 表达式INTERVAL DAY TO SECOND无独立类型
精确小数numericDECIMALNUMBERdecimal
数组一等公民无(用 JSON 模拟)VARRAY / 嵌套表(笨重)无
二进制bytea / 大对象BLOBBLOB / RAWvarbinary

一句话总结:别家有的类型 PG 都有,PG 有的类型别家常常没有。 而且 PostgreSQL 的类型系统是可扩展的——扩展(如 PostGIS 的 geometry)能注册全新类型,和内置类型一样能索引、能运算。

应用场景

  • 用对类型省掉一层代码:IP 白名单用 inet、时长用 interval、金额用 numeric,把「解析 + 校验」从应用层下沉到数据库,少写也少错。
  • 主键选型:分布式生成 ID 用 uuid(注意 v4 的插入局部性问题,规模大时考虑 v7)。
  • 审计与计量:精确金额、精确时长,numeric + interval 是标配。

小结与下一章

PostgreSQL 的「类型即一等公民」不是花哨——它把大量本该在应用层反复解析、校验、易错的逻辑,变成数据库内置的、能索引能约束的能力。这是它区别于其它库的第一层护城河。

类型里最能改变你建模方式的,是数组——它让「多对多」这种经典关系建模,在很多场景下直接省掉一张中间表。下一章就看它:👉 数组运算:把「多对多」压进一列。

参考文献

本页目录