约束(Constraint)是数据库保证数据完整性的机制,让数据库在写入时自动校验,避免非法数据进入表。它是关系数据库"强约束"价值的核心体现。本文系统讲解六类常用约束:主键、唯一、非空、默认、检查、外键。
在数据库层面用约束拦截非法数据,而不是完全依赖应用层:
| 约束 | 关键字 | 级别 | 作用 |
|---|---|---|---|
| 主键 | PRIMARY KEY | 表级 | 唯一标识一行,非空且唯一 |
| 唯一 | UNIQUE | 列/表级 | 列值不重复(允许多个 NULL) |
| 非空 | NOT NULL | 列级 | 禁止 NULL |
| 默认 | DEFAULT | 列级 | 未提供值时用默认值 |
| 检查 | CHECK | 表级 | 满足表达式条件 |
| 外键 | FOREIGN KEY | 表级 | 引用另一表,保证参照完整性 |
-- 建表时:列级定义
CREATE TABLE heros (
id INT PRIMARY KEY, -- 列级主键
name VARCHAR(20)
);
-- 建表时:表级定义(支持联合主键)
CREATE TABLE player_game (
player_id INT NOT NULL,
game_id INT NOT NULL,
score INT,
PRIMARY KEY (player_id, game_id) -- 联合主键
);
-- 事后添加/删除
ALTER TABLE heros ADD PRIMARY KEY (id);
ALTER TABLE heros DROP PRIMARY KEY;CREATE TABLE heros (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(20),
PRIMARY KEY (id)
);最佳实践:主键尽量用无业务含义的自增整数或分布式 ID,避免用可变长字符串(影响索引大小与性能)。详见《范式设计》中"键"的概念。
-- 列级
CREATE TABLE users (
id INT PRIMARY KEY,
email VARCHAR(50) UNIQUE
);
-- 表级 + 联合唯一
CREATE TABLE orders (
order_no VARCHAR(32),
user_id INT,
UNIQUE KEY uk_order_no (order_no),
UNIQUE KEY uk_user_date (user_id, order_date) -- 组合唯一
);
-- 事后添加/删除
ALTER TABLE users ADD UNIQUE KEY uk_email (email);
ALTER TABLE users DROP INDEX uk_email;| 特性 | PRIMARY KEY | UNIQUE |
|---|---|---|
| 是否允许 NULL | ❌ | ✅(可多个) |
| 每表数量 | 1 | 多个 |
| 是否自动建索引 | ✅ | ✅ |
| 语义 | 实体标识 | 业务唯一性(如邮箱、订单号) |
典型应用:业务上"不该重复"的字段(手机号、邮箱、订单号)都应用 UNIQUE,靠数据库兜底防止重复数据。
NOT NULL:该列禁止 NULL,必须给值;DEFAULT 值:未显式给值时,数据库填充默认值。CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50) NOT NULL, -- 必填
status TINYINT NOT NULL DEFAULT 1, -- 默认 1
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 默认当前时间
remark VARCHAR(200) DEFAULT '无' -- 可空但有默认
);-- ✅ 推荐:业务无值用"空串/0/默认值"而非 NULL
CREATE TABLE users (
nickname VARCHAR(50) NOT NULL DEFAULT '', -- 而非允许 NULL
score INT NOT NULL DEFAULT 0
);
-- ❌ 不推荐:大量可空字段会让查询/索引复杂化
CREATE TABLE users (
nickname VARCHAR(50) NULL,
score INT NULL
);为什么不建议滥用 NULL:NULL 的三值逻辑(TRUE/FALSE/UNKNOWN)容易导致查询陷阱;NULL 不参与索引的某些优化;
NOT NULL + DEFAULT语义更清晰。详见《数据过滤》中 NULL 的处理。
通过表达式约束列的值范围,数据库在写入时校验。注意:MySQL 8.0.16 起才真正强制执行 CHECK(之前版本包括 5.7 会解析但忽略);SQL Server/Oracle/PostgreSQL 都支持。
-- MySQL 8.0 起强制生效
CREATE TABLE products (
id INT PRIMARY KEY,
price DECIMAL(10,2),
qty INT,
CONSTRAINT chk_price CHECK (price >= 0), -- 价格非负
CONSTRAINT chk_qty CHECK (qty >= 0 AND qty <= 10000)
);
-- 列级 CHECK
CREATE TABLE people (
age INT CHECK (age BETWEEN 0 AND 150),
gender CHAR(1) CHECK (gender IN ('M', 'F'))
);
-- 事后添加(MySQL 8.0)
ALTER TABLE products ADD CONSTRAINT chk_price CHECK (price >= 0);
ALTER TABLE products DROP CHECK chk_price;-- 校验枚举/格式
CREATE TABLE orders (
status VARCHAR(10) CHECK (status IN ('pending', 'paid', 'cancelled')),
email VARCHAR(100) CHECK (email LIKE '%@%')
);MySQL 8.0 注意:8.0.16+ 才强制 CHECK,且要求"每列值满足 CHECK",
REPLACE/INSERT ... ON DUPLICATE KEY UPDATE等也会校验。
保证参照完整性:子表的外键列值必须存在于父表主键/唯一列中,防止"悬空引用"。
MySQL 注意:只有 InnoDB(等支持外键的引擎)真正强制外键,MyISAM 等引擎会解析但忽略外键定义。
CREATE TABLE team (
team_id INT PRIMARY KEY,
team_name VARCHAR(50)
);
-- 建表时定义外键
CREATE TABLE player (
player_id INT PRIMARY KEY,
player_name VARCHAR(50),
team_id INT,
CONSTRAINT fk_team FOREIGN KEY (team_id) REFERENCES team(team_id)
);
-- 事后添加(MySQL 需指定约束名)
ALTER TABLE player
ADD CONSTRAINT fk_team FOREIGN KEY (team_id) REFERENCES team(team_id);| 动作 | 父表删除/更新时子表行为 |
|---|---|
RESTRICT(默认) | 拒绝删除/更新(若子表有引用) |
NO ACTION | 同 RESTRICT |
CASCADE | 级联删除/更新子表对应行 |
SET NULL | 子表外键置 NULL |
SET DEFAULT | 子表外键置默认值(MySQL/InnoDB 实际不执行该动作,父表删除/更新会被拒绝,效果同 RESTRICT) |
-- 父表删球队时,级联删除该队球员
CREATE TABLE player (
player_id INT PRIMARY KEY,
team_id INT,
FOREIGN KEY (team_id) REFERENCES team(team_id)
ON DELETE CASCADE ON UPDATE CASCADE
);-- 物理外键:数据库强制
FOREIGN KEY (team_id) REFERENCES team(team_id)
-- 逻辑外键:应用层维护,字段上只建普通索引
KEY idx_team_id (team_id)| 方式 | 优点 | 缺点 |
|---|---|---|
| 物理外键 | 数据库保证一致性、自动级联 | 影响写入性能、分布式/分库分表不适用、耦合高 |
| 逻辑外键 | 灵活、性能好 | 需应用层保证一致性,可能产生脏数据 |
本节从约束视角概览外键;外键的完整专题分析(多数据库差异、级联场景、逻辑外键 vs 物理外键取舍、最佳实践)见《21-数据库外键》。
把六类约束用在一个订单系统上:
CREATE TABLE users (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
email VARCHAR(100) NOT NULL,
phone VARCHAR(20),
status TINYINT NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uk_email (email),
CONSTRAINT chk_status CHECK (status IN (0, 1, 2))
) ENGINE=InnoDB;
CREATE TABLE orders (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
user_id BIGINT UNSIGNED NOT NULL,
amount DECIMAL(12,2) NOT NULL,
status VARCHAR(10) NOT NULL DEFAULT 'pending',
paid_at DATETIME NULL,
PRIMARY KEY (id),
KEY idx_user (user_id),
CONSTRAINT chk_amount CHECK (amount >= 0),
CONSTRAINT chk_order_status CHECK (status IN ('pending','paid','cancelled')),
CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id)
) ENGINE=InnoDB;| 完整性 | 机制 | 约束 |
|---|---|---|
| 实体完整性 | 每行有唯一标识 | PRIMARY KEY、UNIQUE |
| 域完整性 | 列值符合规定 | NOT NULL、DEFAULT、CHECK、数据类型 |
| 参照完整性 | 外键引用有效 | FOREIGN KEY |
| 用户定义完整性 | 业务规则 | CHECK + 应用逻辑 |
| 约束 | 关键字 | 作用 | MySQL 8.0 |
|---|---|---|---|
| 主键 | PRIMARY KEY | 非空唯一标识 | ✅ |
| 唯一 | UNIQUE | 值不重复(允许多个 NULL) | ✅ |
| 非空 | NOT NULL | 禁止 NULL | ✅ |
| 默认 | DEFAULT | 缺省值 | ✅ |
| 检查 | CHECK | 值满足表达式 | ✅(8.0.16+ 强制) |
| 外键 | FOREIGN KEY | 参照完整性 | ✅ |
NOT NULL + DEFAULT 是消除 NULL 陷阱的推荐组合;