PostgreSQL ENUM、CHECK 约束和字典表应该怎么选?
简化版
PostgreSQL ENUM 适合稳定、少变化的取值;CHECK 适合简单约束且便于随 DDL 变更;字典表适合运营可配置、需要展示文案、排序、启停和扩展属性的取值。核心取舍是稳定性、变更成本和治理需求。
详细版
状态字段可以用 ENUM、CHECK 或字典表表达。ENUM 类型语义强,存储紧凑,但修改取值需要 DDL,删除或重命名值比较麻烦。
CHECK (status IN (...)) 简单直观,约束在表上,适合少量稳定值。字典表最灵活,但需要 join 或缓存,并且强一致约束需要外键或应用校验。
面试时不要说某一种绝对最好。订单状态这种强业务状态可用 CHECK 或 ENUM,渠道来源、行业分类这类配置更适合字典表。
完整版教学
一、为什么同一个状态有多种建模方式
数据库字段取值有限时,需要在“约束强度”和“变更灵活性”之间取舍。约束越强,脏数据越少;变更越灵活,运营和业务迭代越方便。
PostgreSQL 提供 ENUM、CHECK、外键字典表等方式。它们看似都能限制取值,实际维护体验完全不同。
选型前先问:这个值会不会频繁新增?是否需要展示文案?是否参与核心状态机?
二、ENUM 的特点
ENUM 是数据库类型,字段只能存该类型定义的值。
CREATE TYPE order_status AS ENUM ('CREATED', 'PAID', 'CLOSED');
CREATE TABLE orders (
id bigint PRIMARY KEY,
status order_status NOT NULL
);
优点是语义强、类型清楚。缺点是枚举值演进需要 DDL,历史版本兼容和回滚要谨慎。
三、CHECK 约束的特点
CHECK 直接在表字段上声明条件:
CREATE TABLE orders (
id bigint PRIMARY KEY,
status text NOT NULL,
CHECK (status IN ('CREATED', 'PAID', 'CLOSED'))
);
它比 ENUM 更像表级规则,修改约束也需要 DDL,但对应用类型绑定较少。适合取值少、变化不频繁的字段。
四、字典表的特点
字典表把取值变成数据:
dict_type = order_source
code = app
name = App 下单
enabled = true
sort_no = 10
它能支持展示名、排序、多语言、启停、备注等扩展属性。代价是查询或应用层要翻译 code,完整性要靠外键或校验。
| 方案 | 适合 | 不适合 |
|---|---|---|
| ENUM | 极稳定状态 | 频繁变更 |
| CHECK | 简单有限值 | 需要展示配置 |
| 字典表 | 可配置取值 | 强状态机随意扩展 |
五、状态机字段要特别谨慎
订单状态、支付状态不是普通字典。它们控制业务流程,新增一个状态往往意味着代码、消息、报表、权限都要改。
这类字段不应该让运营直接在字典表加值后立刻生效。即使用字典表展示名称,状态流转规则仍要在代码或状态机配置中严格控制。
数字例子:订单状态从 5 个变成 6 个,涉及创建、支付、取消、退款、售后 5 条链路;不是加一行字典那么简单。
六、常见误区与追问
- 误区:ENUM 性能好,所以所有状态都用 ENUM。 变更成本和回滚风险也要考虑。
- 误区:字典表最灵活,所以能替代状态机。 核心状态流转不能只靠字典项控制。
- 误区:CHECK 约束没必要。 它能阻止非法值进入数据库,是很直接的数据保护。
- 追问:运营可配置取值用什么? 通常用字典表,支持名称、排序、启停和扩展属性。
- 追问:稳定技术状态用什么? ENUM 或 CHECK 都可以,取决于团队对 DDL 演进的接受度。
七、加强记忆
记忆钩子:ENUM 像刻在数据库类型里的名单,CHECK 像门口检查规则,字典表像后台可维护的花名册。
回答这题按“稳定性、变更成本、展示治理、状态机边界”来选型。这样能体现你不是为了炫 PostgreSQL 特性,而是在做可维护的数据建模。