SQL约束有哪些类型及作用
SQLPro 是一款macOS平台上数据库管理应用,避免使用非法,享受更全面的支持和更新。它更贴近日常 macOS 使用场景,处理高频操作时整体会更顺手一些。
刚开始设计关系型数据库?了解PRIMARY KEY、FOREIGN KEY、UNIQUE、NOT NULL、CHECK、DEFAULT等约束的作用、语法和排错方法。本文以PostgreSQL语法为主,结合用户订单场景,清晰展示如何防止脏数据并处理常见约束冲突。
很多初学者在设计数据库表时,倾向于把所有数据校验逻辑都交给后端代码处理,认为数据库只负责存储。这种想法会导致数据一致性脆弱,一旦应用层出现漏洞或多人同时写入,数据库中就会产生大量脏数据。
SQL 约束(Constraints)是数据库层面的“守门员”,它们在数据写入或更新时自动执行规则,确保数据的完整性、唯一性和有效性。无论应用层如何变化,数据库约束都能保证核心业务规则不被破坏。
先分清约束各自保护什么
在关系型数据库中,最基础的约束主要解决“我是谁”、“我在哪”以及“我不能为空”的问题。我们以常见的 users(用户表)和 orders(订单表)为例。
PRIMARY KEY(主键) 是唯一标识每一行数据的列。它的核心要求是值必须唯一且不能为 NULL。一张表通常只有一个主键,它是其他表引用该表记录的基石。
FOREIGN KEY(外键) 用于建立两张表之间的关联。它保证子表(如 orders)中的某个值(如 user_id)必须在父表(如 users)的主键中存在。这防止了“孤儿记录”的产生,即订单指向了一个不存在的用户。
UNIQUE(唯一约束) 限制一列或多列组合的值不能重复。与主键不同,唯一约束允许值为 NULL(具体行为视数据库而定),且一张表可以有多个唯一约束。例如,用户的邮箱地址通常需要具备唯一性。
NOT NULL(非空约束) 最简单直接,它禁止列中存入 NULL 值。对于必填字段,如用户名或订单金额,必须加上此约束。
下面是一段基于 PostgreSQL 语法的建表语句,展示了这些基础约束的位置:
CREATE TABLE users (
id SERIAL PRIMARY KEY, -- 主键:唯一且非空
username VARCHAR(50) NOT NULL, -- 非空:用户名必填
email VARCHAR(100) UNIQUE -- 唯一:邮箱不能重复
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
amount DECIMAL(10, 2) NOT NULL,
-- 外键:user_id 必须存在于 users.id 中
CONSTRAINT fk_user
FOREIGN KEY (user_id)
REFERENCES users(id)
);

主键、外键、唯一和非空约束共同保护表数据
在上图中,我们可以直观地看到 users.id 作为主键被 orders.user_id 引用。如果尝试插入一个 user_id 为 999 的订单,而 users 表中没有 id 为 999 的用户,数据库会立即拒绝该操作并抛出外键约束错误。
值域约束:CHECK 与 DEFAULT
除了结构性的关联约束,我们还需要控制数据的具体内容是否符合业务逻辑。这时就需要用到 CHECK 和 DEFAULT 约束。
CHECK 约束 允许你定义一个布尔表达式,只有当表达式结果为真时,数据才能被写入。它常用于限制数值范围或枚举状态。例如,订单状态只能是“待支付”或“已支付”,订单数量必须大于零。
DEFAULT 约束 指定了在插入数据时,如果未提供该列的值,数据库应自动填充的默认值。需要注意的是,DEFAULT 仅在省略该列时生效。如果你显式地插入了 NULL,且该列允许为空,那么存入的就是 NULL,而不是默认值。
以下示例展示了如何在 orders 表中应用这两种约束:
ALTER TABLE orders
ADD COLUMN status VARCHAR(20) DEFAULT 'pending'
CHECK (status IN ('pending', 'paid', 'cancelled'));
ALTER TABLE orders
ADD COLUMN quantity INT DEFAULT 1
CHECK (quantity > 0);
-- 添加创建时间,默认为当前时间
ALTER TABLE orders
ADD COLUMN created_at TIMESTAMP DEFAULT NOW();

CHECK 拒绝越界值,DEFAULT 填充省略字段
如图所示,当执行插入操作时:
- 如果省略
status和created_at,数据库会自动填入'pending'和当前时间戳。 - 如果尝试插入
quantity = -5,CHECK 约束会拦截该请求,因为-5 > 0为假。 - 如果显式插入
status = 'shipped',而该值不在 CHECK 允许的列表中,操作同样会失败。
这种机制将业务规则下沉到数据库层,即使后端代码逻辑出错,也能保证数据库中不会出现数量为负数的订单或非法的状态值。
约束组合与跨库差异
在实际工程中,约束往往不是孤立存在的,而是组合使用。同时,不同的数据库管理系统(DBMS)对某些约束的处理细节存在差异,开发时需特别注意。
复合唯一约束
有时单个列无法保证唯一性,需要多列组合。例如,一个用户在同一个日期下只能有一个特定类型的订单。可以使用复合 UNIQUE 约束:
ALTER TABLE orders
ADD CONSTRAINT unique_user_date_type
UNIQUE (user_id, order_date, type);
外键的级联行为
当父表记录被删除或更新时,子表该如何反应?可以在定义 FOREIGN KEY 时配置 ON DELETE 和 ON UPDATE 行为:
CASCADE:父表删除,子表对应记录也删除。SET NULL:父表删除,子表对应字段设为 NULL(前提是该字段允许为空)。RESTRICT或NO ACTION:默认行为,如果子表有引用,则禁止删除父表记录。
CONSTRAINT fk_user
FOREIGN KEY (user_id)
REFERENCES users(id)
ON DELETE CASCADE
ON UPDATE CASCADE;
跨数据库的关键差异
虽然 SQL 标准定义了这些约束,但实现细节有所不同:
-
UNIQUE 与 NULL:
- 在 PostgreSQL 和 SQL Server 中,UNIQUE 约束允许有多个 NULL 值,因为 NULL 不等于 NULL。
- 在 MySQL (InnoDB) 中,行为类似,但需注意旧版本或特定引擎的差异。
- 在 SQLite 中,UNIQUE 约束通常也允许多个 NULL,但在某些严格模式下可能表现不同。
-
CHECK 约束的支持:
- PostgreSQL、SQL Server 和 SQLite 完整支持 CHECK 约束。
- MySQL 在 8.0.16 之前版本中,虽然解析 CHECK 语法,但会忽略其检查逻辑。如果在老版本 MySQL 上依赖 CHECK 保证数据质量,将会导致数据污染。
-
默认表达式:
- 大多数数据库支持
NOW()、CURRENT_TIMESTAMP等函数作为默认值。 - 部分数据库不支持复杂的表达式作为 DEFAULT,仅支持常量或简单函数。
- 大多数数据库支持
因此,在迁移数据库或编写通用 SQL 时,务必查阅目标数据库的官方文档,特别是关于 CHECK 和 NULL 处理的章节。
遇到约束报错怎么定位
当应用程序抛出“Constraint Violation”或“Integrity Error”时,不要慌张。按照以下步骤可以快速定位问题:
-
识别约束名称: 数据库报错信息中通常会包含违反的约束名,如
violates foreign key constraint "fk_user"或violates check constraint "orders_quantity_check"。如果没有自定义约束名,数据库会自动生成一个随机名称,此时需查看表定义。 -
检查表定义: 使用
\d table_name(PostgreSQL) 或SHOW CREATE TABLE table_name(MySQL) 查看表的结构,确认哪些列有 NOT NULL、UNIQUE 或 CHECK 限制。 -
核对输入数据:
- NOT NULL 错误:检查是否传入了 NULL 或空字符串,而该列不允许为空。
- UNIQUE/PRIMARY KEY 错误:检查数据库中是否已存在相同值的记录。
- FOREIGN KEY 错误:检查引用的父表 ID 是否存在。例如,插入订单前,确认用户 ID 已在
users表中。 - CHECK 错误:检查数值是否超出范围,或字符串是否在允许的枚举列表内。
-
修正数据或逻辑:
- 如果是测试数据,清理冲突记录。
- 如果是业务逻辑问题,需在应用层增加预校验,或在数据库中调整约束条件(如扩展 CHECK 的范围)。
重要提示:应用层的校验不能替代数据库约束。应用层校验可以提升用户体验(快速反馈),但数据库约束是数据一致性的最后一道防线。两者应配合使用,而非二选一。
通过合理使用 PRIMARY KEY、FOREIGN KEY、UNIQUE、NOT NULL、CHECK 和 DEFAULT,你可以构建出一个健壮、自解释且难以被破坏的数据库结构。在面对报错时,理解约束的本质能帮助你更快地从数据层面找到根源。
参考资料
- PostgreSQL constraints
- MySQL CREATE TABLE constraints
- SQL Server constraints
- SQLite CREATE TABLE constraints










































