创建时间: 2026-09-01最后更新: 2026-09-01

1. 约束

类型只能表达一部分规则。status text 可以保证状态是文本,却不能阻止 unknown-value 被写入;email text 可以保存邮箱,却不能保证两个用户不会使用相同邮箱。

约束是由数据库执行的数据规则。无论写入来自 FastAPI、后台脚本、迁移工具还是人工 SQL,只要不满足规则,PostgreSQL 就会拒绝操作。

应用层校验仍然有意义。Pydantic 可以在请求刚进入接口时返回更友好的错误,但它无法完全避免并发问题,也无法约束绕过接口的其他写入。可靠项目通常采用两层保护:

validation-layers.txt
1
FastAPI / Pydantic:尽早发现错误,提供友好提示
2
PostgreSQL 约束:守住最终的数据一致性

约束可以直接写在字段后面,也可以单独写成表级约束。表级写法适合为约束命名,也适合定义涉及多个字段的规则。它的基本格式是 CONSTRAINT 约束名 约束定义。约束名由开发者决定,但通常会包含表名、字段名和约束类型,方便我们根据数据库报错快速定位对应规则。

先来看后文项目表中会使用的两条表级约束:

project-constraints.sql
1
CONSTRAINT projects_pkey PRIMARY KEY (id),
2
CONSTRAINT projects_owner_fkey
3
FOREIGN KEY (owner_id)
4
REFERENCES users (id)
5
ON DELETE RESTRICT

这几行分别表达了两条约束:

  • CONSTRAINT projects_pkey PRIMARY KEY (id):创建名为 projects_pkey 的主键约束,将 projects.id 设为主键。数据库会要求 id 唯一且不能为 NULL,PostgreSQL 还会自动为主键创建唯一 B-tree 索引。
  • CONSTRAINT projects_owner_fkey:开始定义名为 projects_owner_fkey 的外键约束。换行只是为了便于阅读,不会改变 SQL 的含义。
  • FOREIGN KEY (owner_id):指定当前表 projects 中的 owner_id 是外键列。
  • REFERENCES users (id):指定 owner_id 引用 users.id。因此,项目中填写的 owner_id 必须能在用户表的 id 列中找到。
  • ON DELETE RESTRICT:当某个用户仍被项目引用时,拒绝删除该用户。例如,项目的 owner_id 为 1,那么删除 users.id = 1 的用户会失败。它不会阻止删除这个项目,也不会阻止删除没有项目引用的用户。

合在一起,这段 SQL 表达的关系是:projects.id 唯一标识一个项目,而 projects.owner_id 必须指向一个真实存在的用户;只要项目还属于该用户,就不能直接删除这个用户。

2. 常用约束

先看一张包含常用约束的用户表:

create-users.sql
1
CREATE TABLE users (
2
id bigint GENERATED ALWAYS AS IDENTITY,
3
email text NOT NULL,
4
display_name text NOT NULL,
5
created_at timestamptz NOT NULL DEFAULT now(),
6
CONSTRAINT users_pkey PRIMARY KEY (id),
7
CONSTRAINT users_email_key UNIQUE (email),
8
CONSTRAINT users_display_name_length CHECK (char_length(display_name) >= 2)
9
);

这段 SQL 包含四类约束:

约束作用
PRIMARY KEY唯一标识一行,并且不允许 NULL
NOT NULL要求该列必须有值
UNIQUE默认不允许非 NULL 值重复;可空列可以有多个 NULL
CHECK要求表达式结果不能为 false;结果为 true 或 NULL 都会通过

DEFAULT 经常与约束一起出现,但它本身不负责验证。默认值只在插入时省略字段时提供一个值;真正保证字段不能为空的是 NOT NULL。

一张表最多只能有一个主键,但可以有多个 UNIQUE 约束。UNIQUE 默认把两个 NULL 当作不同的值,因此可空的唯一列可以出现多行 NULL。如果业务要求最多只能有一行 NULL,PostgreSQL 15 及更高版本可以使用 NULLS NOT DISTINCT:

unique-null.sql
1
CREATE TABLE user_profiles (
2
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
3
external_id text UNIQUE NULLS NOT DISTINCT
4
);

CHECK 约束也要注意 NULL。例如 CHECK (price > 0) 并不会阻止 price 为 NULL,因为表达式结果是 NULL 而不是 false。既要求有值又要求满足范围时,应同时声明 NOT NULL 和 CHECK。

正在验证登录状态
请稍候,验证完成后将继续显示文章内容