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

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

- 作者：David（道雾轩）
- 专栏：PostgreSQL 特性详解（https://daiw.org/manual/postgresql.md）
- 最后更新：2026-07-13
- 原文：https://daiw.org/manual/postgresql/rich-types
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

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

如果只能用一句话概括 PostgreSQL 和别家的差别，我会说：**在 PG 里，「类型」是可以无限扩展的一等公民，而不是几个内置基本类型的固定清单。**

一个 IP 地址、一段时间间隔、一个 128 位的唯一 ID、一段任意精度的金额——在很多数据库里，这些要么塞进 `VARCHAR` 靠应用层解释，要么用 `DECIMAL` 勉强凑。而在 PostgreSQL 里，它们各自是**真正的类型**：有专属的存储格式、专属的运算符、能建索引、能进约束。

## 一行数据，把「类型齐全」摊开看

我们的实测里，先插入了一行「什么类型都有」的数据，再取回来：

```text
取回: 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 个字符。

```sql
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 一步到位。

<Callout type="info">
  UUID 做主键要小心一个性能坑：**完全随机的 UUID(v4)会让 B-tree 主键索引的插入点四处乱跳**，页面频繁分裂、缓存命中率差。想用 UUID 又想要顺序性，可考虑 **UUIDv7**（时间有序，PG 18 已可用相关扩展/函数生成），兼得唯一性与「近似自增」的插入局部性。
</Callout>

## INET / CIDR：IP 是类型，不是字符串

把 IP 存成字符串，你就没法问「这个地址在不在那个网段里」——只能取回来在应用层解析。PostgreSQL 的 `inet` / `cidr` 类型自带网络语义：

```sql
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):

```sql
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`)是**任意精度的十进制**，精确到你指定的小数位：

```text
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** 机制自动把超长值挪到附属存储（见 [索引一章](https://daiw.org/manual/postgresql/indexes) 会再提到 TOAST），对你透明。

## 还有一批，后面专章讲

丰富类型里最能打的几个，值得各开一章：

- **数组**(`int[]` / `text[]` / `jsonb[]`)—— 标签、权限位、多选枚举，免掉多对多映射表。见 [数组运算](https://daiw.org/manual/postgresql/arrays)。
- **JSONB** —— 把 PG 从「关系库」扩展成「关系 + 文档」混合库。见 [JSONB](https://daiw.org/manual/postgresql/jsonb)。
- **范围类型**(`int4range` / `tsrange`)+ 生成列 —— 会议室不许时段重叠这种约束，数据库层面强制。见 [范围类型与生成列](https://daiw.org/manual/postgresql/range-generated)。

## 横向对比

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

类型里最能改变你建模方式的，是**数组**——它让「多对多」这种经典关系建模，在很多场景下直接省掉一张中间表。下一章就看它：👉 [数组运算：把「多对多」压进一列](https://daiw.org/manual/postgresql/arrays)。

## 参考文献

- [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 生成。
