MySQL 约束完全指南
本文档专门讲 约束(Constraint):什么是约束、有哪些种类、怎么在建表或改表时添加,以及常见用法和注意点。每一步都配有详细说明和大量示例,适合零基础新手跟着做。
目录
- 什么是约束
- 约束有哪些种类
- 主键约束(PRIMARY KEY)
- 唯一约束(UNIQUE)
- 非空约束(NOT NULL)
- 默认值(DEFAULT)
- 检查约束(CHECK)
- 外键约束(FOREIGN KEY)
- 约束的添加与删除
- 查看约束
- 约束命名与最佳实践
- 常见错误与注意点
- 综合示例与速查表
1. 什么是约束
1.1 定义
约束就是给表或列加的规则,用来限制能存什么样的数据,从而保证数据正确、一致、完整。
- 例如:主键不能重复、不能为空;年龄不能是负数;性别只能是“男”或“女”。
- 约束由数据库在插入、更新时自动检查:不符合规则的操作会报错,数据就不会被错误写入。
1.2 为什么需要约束
- 防止脏数据:如年龄写成 -1、重复的学号、空的主键。
- 保证关系正确:如“订单里的用户 id 必须在用户表里存在”(外键)。
- 让业务含义更清晰:NOT NULL 表示“这项必填”,UNIQUE 表示“不能重复”。
1.3 约束作用在谁身上
- 有的约束作用在一列上(如 NOT NULL、UNIQUE、DEFAULT)。
- 有的作用在一行或多列组合上(如 PRIMARY KEY、UNIQUE(列1,列2)、FOREIGN KEY)。
下面按种类逐个讲。
2. 约束有哪些种类
| 约束类型 | 关键字 | 作用简述 |
|---|---|---|
| 主键约束 | PRIMARY KEY | 唯一标识一行,不能重复、不能为空 |
| 唯一约束 | UNIQUE | 该列(或列组合)的值不能重复 |
| 非空约束 | NOT NULL | 该列必须有值,不能为 NULL |
| 默认值 | DEFAULT | 插入时不写该列,用默认值 |
| 检查约束 | CHECK | 该列(或行)必须满足一个条件 |
| 外键约束 | FOREIGN KEY | 该列的值必须在另一张表的某列中存在 |
说明:默认值(DEFAULT)在很多书里不算“约束”,而是“列属性”,但和 NOT NULL 等一起用,习惯上会一起讲,本文也放在一起说明。
3. 主键约束(PRIMARY KEY)
3.1 作用
- 主键用来唯一标识表中的每一行。
- 要求:不能重复、不能为 NULL。
- 一张表最多只能有一个主键,但主键可以由一列或多列组合构成。
3.2 单列主键(最常用)
写法一:写在列定义后面
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(50),
password VARCHAR(100)
);
写法二:在表末尾单独写(适合多列主键或起名)
CREATE TABLE users (
id INT,
username VARCHAR(50),
password VARCHAR(100),
PRIMARY KEY (id)
);
示例:主键 + 自增(最常见)
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50),
password VARCHAR(100)
);
插入时可以不写 id,数据库会自动从 1 递增。
3.3 复合主键(多列一起做主键)
当“单列”无法唯一标识一行时,可以用多列组合作为主键:
CREATE TABLE score (
student_id INT,
subject VARCHAR(50),
score DECIMAL(5,2),
PRIMARY KEY (student_id, subject)
);
表示:学号 + 科目 一起唯一,即“每个学生每门课只有一条成绩记录”。
3.4 主键与 NULL、重复
- 主键列不能为 NULL,插入 NULL 会报错。
- 主键列不能重复,插入或更新成已有值会报错。
-- 错误:主键为 NULL(若 id 是主键且非自增)
INSERT INTO users (id, username) VALUES (NULL, 'test');
-- 错误:主键重复
INSERT INTO users (id, username) VALUES (1, 'a');
INSERT INTO users (id, username) VALUES (1, 'b'); -- 报错:Duplicate entry '1' for key 'PRIMARY'
3.5 主键小结
- 每张表建议有且仅有一个主键;常用自增整数列 id 作为主键。
- 主键 = 唯一 + 非空;可单列,可多列(复合主键)。
4. 唯一约束(UNIQUE)
4.1 作用
- UNIQUE 表示:该列(或列组合)的值不能重复。
- 与主键区别:一张表可以有多个 UNIQUE;UNIQUE 列可以为 NULL(在 MySQL 里多行 NULL 是否算“重复”取决于版本和设置,一般唯一列只允许一个 NULL 或都允许 NULL,视实现而定)。
4.2 单列唯一
示例:用户名不能重复
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE,
email VARCHAR(100) UNIQUE,
password VARCHAR(100)
);
插入重复的 username 或 email 会报错:Duplicate entry 'xxx' for key 'username'。
示例:给唯一约束起名(表级写法)
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50),
email VARCHAR(100),
UNIQUE KEY uk_username (username),
UNIQUE KEY uk_email (email)
);
4.3 多列联合唯一(UNIQUE(列1, 列2))
表示:列1 和 列2 的组合不能重复,单列可以重复。
示例:同一用户对同一商品只能有一条“收藏”记录
CREATE TABLE favorite (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
product_id INT,
UNIQUE KEY uk_user_product (user_id, product_id)
);
user_id=1, product_id=2只能出现一次;user_id=1, product_id=3可以再有一条;- 即“用户+商品”组合唯一。
4.4 UNIQUE 与 NULL
在 MySQL 中,UNIQUE 列可以存 NULL;多行 NULL 在唯一性上的处理:有的版本允许多个 NULL(把 NULL 视为彼此不相等),有的只允许一个 NULL,需以当前版本为准。若业务上“不允许重复且允许为空”,一般会配合 NOT NULL 或应用层校验。
5. 非空约束(NOT NULL)
5.1 作用
- NOT NULL 表示:该列必须有值,不能插入或更新为 NULL。
5.2 基本用法
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
password VARCHAR(100) NOT NULL,
nickname VARCHAR(50) -- 可空,不写 NOT NULL
);
插入时若 username 或 password 为 NULL,会报错:Column 'username' cannot be null。
5.3 与 DEFAULT 配合
“不能为空”又希望有默认值时,可同时写 NOT NULL 和 DEFAULT:
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT '待支付',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
这样 status 和 created_at 不写也会自动填值,且不允许被更新为 NULL。
5.4 空字符串与 NULL
- NULL 表示“没有值、未知”。
- 空字符串
''是一个值,只是长度为 0。 - NOT NULL 只禁止 NULL,不禁止空字符串;若业务上“既不能为 NULL 也不能为空串”,可以在应用层检查,或用 CHECK(见下节)限制。
6. 默认值(DEFAULT)
6.1 作用
- DEFAULT 值:插入时若没有指定该列,就使用默认值;若显式写了值(包括 NULL),则以写的为准。
6.2 基本用法
CREATE TABLE product (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
stock INT DEFAULT 0,
status VARCHAR(20) DEFAULT '上架',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
示例插入:
INSERT INTO product (name) VALUES ('手机');
-- stock 为 0,status 为 '上架',created_at 为当前时间
6.3 常用默认值示例
| 类型 | 默认值写法示例 | 说明 |
|---|---|---|
| 数字 | DEFAULT 0 |
数量、状态码等 |
| 字符串 | DEFAULT '待处理' |
状态、类型等 |
| 当前时间 | DEFAULT CURRENT_TIMESTAMP |
创建时间 |
| 当前日期 | DEFAULT (CURDATE()) |
仅日期(MySQL 8.0+) |
示例:
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -- 更新时自动改时间(TIMESTAMP 支持)
6.4 DEFAULT 与 NULL
- 若列没有写 DEFAULT,且没有 NOT NULL,不写该列时默认就是 NULL。
- 若写了 DEFAULT 值,不写该列时用默认值;若想“允许为空且默认也是空”,可以写
DEFAULT NULL(一般省略不写)。
7. 检查约束(CHECK)
7.1 作用(MySQL 8.0.16+)
- CHECK 表示:该列(或行)的值必须满足一个条件表达式,不满足则插入/更新报错。
- MySQL 在 8.0.16 及以后版本才真正支持 CHECK(之前版本会忽略)。
7.2 单列 CHECK
示例:年龄必须在 0 到 150 之间
CREATE TABLE person (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
age INT CHECK (age >= 0 AND age <= 150)
);
或表级写法(可起名):
CREATE TABLE person (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
age INT,
CONSTRAINT chk_age CHECK (age >= 0 AND age <= 150)
);
示例:性别只能是 男 或 女
CREATE TABLE person (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
gender CHAR(1) CHECK (gender IN ('男', '女'))
);
示例:成绩 0~100
score DECIMAL(5,2) CHECK (score >= 0 AND score <= 100)
7.3 多列 CHECK(表级)
示例:结束时间必须大于开始时间
CREATE TABLE event (
id INT PRIMARY KEY AUTO_INCREMENT,
start_at DATETIME,
end_at DATETIME,
CONSTRAINT chk_time CHECK (end_at > start_at)
);
7.4 低版本 MySQL
若使用 8.0.16 之前的版本,CHECK 会被忽略,同类规则需要在应用层或触发器里实现。
8. 外键约束(FOREIGN KEY)
8.1 作用与概念
- 外键表示:本表某列(或列组合)的取值,必须在另一张表的主键或唯一键中存在。
- 用来保证“引用关系”正确,例如:订单表的
user_id必须在用户表里有对应 id。
术语:
- 主表(父表):被引用的表(如 users)。
- 从表(子表):引用别人的表(如 orders)。
- 外键列:从表中存“主表主键值”的列(如 orders.user_id)。
8.2 基本语法
CREATE TABLE 从表 (
...
外键列 类型,
FOREIGN KEY (外键列) REFERENCES 主表(主表主键列)
[ON DELETE 规则] [ON UPDATE 规则]
);
8.3 示例:订单表引用用户表
先建主表:
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL
);
再建从表,并建外键:
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
amount DECIMAL(10,2),
FOREIGN KEY (user_id) REFERENCES users(id)
);
orders.user_id的值必须存在于users.id中。- 若插入
orders时user_id在users里不存在,会报错。 - 若删除
users中某行,而orders里还有行引用该 id,默认会报错,防止“孤儿数据”。
8.4 ON DELETE 和 ON UPDATE(引用行为)
当主表中被引用的行被删除(DELETE)或更新(UPDATE)时,从表怎么处理,由 ON DELETE / ON UPDATE 决定。
| 规则 | 含义(简述) |
|---|---|
| RESTRICT | 默认;若从表有引用,禁止主表删除/更新 |
| CASCADE | 主表删/改时,从表对应行一起删/改 |
| SET NULL | 主表删/改时,把从表外键列设为 NULL |
| NO ACTION | 类似 RESTRICT |
示例:主表删除用户时,把订单的 user_id 设为 NULL(需允许 user_id 为 NULL)
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT, -- 允许 NULL
amount DECIMAL(10,2),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
);
示例:主表删除用户时,一起删除其订单(级联删除)
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
注意:CASCADE 会连带着删/改从表数据,使用时要特别小心。
8.5 外键约束名(可选)
可以给外键起名,方便以后删除或查看:
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
amount DECIMAL(10,2),
CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES users(id)
);
8.6 何时用外键
- 优点:数据库层面保证引用正确,避免“无效的 user_id”等。
- 缺点:影响插入/更新/删除性能,主从表顺序和迁移更麻烦。
- 建议:小型项目、对一致性要求高时可用;大型高并发系统有时只在应用层保证关系,不用外键。新手可以先会用语法,再根据项目选择。
9. 约束的添加与删除
9.1 建表时添加(上文已大量示例)
在 CREATE TABLE 的列定义或表级子句中写 PRIMARY KEY、UNIQUE、NOT NULL、DEFAULT、CHECK、FOREIGN KEY 即可。
9.2 已有表:用 ALTER TABLE 添加
添加主键(表还没有主键时):
ALTER TABLE users ADD PRIMARY KEY (id);
添加唯一:
ALTER TABLE users ADD UNIQUE KEY uk_username (username);
添加非空(列当前没有 NULL 值时才容易成功):
ALTER TABLE users MODIFY COLUMN username VARCHAR(50) NOT NULL;
添加默认值:
ALTER TABLE users MODIFY COLUMN status VARCHAR(20) DEFAULT '正常';
添加检查约束(MySQL 8.0.16+):
ALTER TABLE person ADD CONSTRAINT chk_age CHECK (age >= 0 AND age <= 150);
添加外键:
ALTER TABLE orders ADD CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id);
9.3 删除约束
删除主键:
ALTER TABLE users DROP PRIMARY KEY;
删除唯一约束(通过约束名或索引名):
ALTER TABLE users DROP INDEX uk_username;
删除外键(用约束名):
ALTER TABLE orders DROP FOREIGN KEY fk_orders_user;
取消非空 / 默认值: 用 MODIFY 把列改回可空或去掉 DEFAULT,例如:
ALTER TABLE users MODIFY COLUMN nickname VARCHAR(50) NULL;
删除 CHECK(MySQL 8.0.16+):
ALTER TABLE person DROP CONSTRAINT chk_age;
10. 查看约束
10.1 用 SHOW CREATE TABLE
SHOW CREATE TABLE 表名;
会显示建表语句,其中包含 PRIMARY KEY、UNIQUE、FOREIGN KEY 等定义。
10.2 用 information_schema(了解)
约束信息存在系统库 information_schema 里,例如:
SELECT * FROM information_schema.TABLE_CONSTRAINTS
WHERE TABLE_SCHEMA = '你的数据库名' AND TABLE_NAME = '你的表名';
可查到主键、唯一、外键、CHECK 的约束名和类型。列上的 NOT NULL、DEFAULT 在 COLUMNS 表里。
11. 约束命名与最佳实践
11.1 约束命名(可选但推荐)
- 主键:常省略名字,或叫
pk_表名。 - 唯一:
uk_表名_列名或uk_列名。 - 外键:
fk_从表_主表或fk_列名。 - 检查:
chk_表名_列名或chk_描述。
命名后,删除、查看都会更清晰。
11.2 最佳实践小结
- 每张表尽量有一个主键,常用自增 id。
- 业务上“不能重复”的列加 UNIQUE(如用户名、邮箱)。
- 必填列加 NOT NULL;有合理默认值的加 DEFAULT。
- 取值范围固定时可用 CHECK(MySQL 8.0.16+)或 ENUM。
- 外键按需使用,并明确 ON DELETE / ON UPDATE 行为。
- 加约束前确认现有数据符合约束,否则 ALTER 可能失败(如给已有 NULL 的列加 NOT NULL)。
12. 常见错误与注意点
12.1 主键重复或为 NULL
插入/更新时主键重复或写 NULL(且非自增)会报错,检查 id 是否重复、是否误写 NULL。
12.2 唯一列重复
UNIQUE 列插入已存在的值会报错;若业务允许“重复则更新”,可用 INSERT … ON DUPLICATE KEY UPDATE(非本文重点)。
12.3 外键引用不存在的值
插入从表时,外键列的值必须在主表中存在;删除主表行时,若从表有引用且为 RESTRICT,会报错。
12.4 给已有 NULL 的列加 NOT NULL
先更新数据把 NULL 改成有效值,再 MODIFY … NOT NULL。
12.5 CHECK 在低版本不生效
MySQL 8.0.16 之前不支持 CHECK,需用应用层或触发器实现同类逻辑。
13. 综合示例与速查表
13.1 综合建表示例
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) UNIQUE,
password VARCHAR(100) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT '正常',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
amount DECIMAL(10,2) NOT NULL CHECK (amount >= 0),
status VARCHAR(20) NOT NULL DEFAULT '待支付',
CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE RESTRICT
);
13.2 约束速查表
| 约束 | 作用 | 建表示例(单列) |
|---|---|---|
| 主键 | 唯一、非空 | id INT PRIMARY KEY AUTO_INCREMENT |
| 唯一 | 不可重复 | username VARCHAR(50) UNIQUE |
| 非空 | 不能为 NULL | name VARCHAR(50) NOT NULL |
| 默认值 | 未指定时用默认 | status VARCHAR(20) DEFAULT '正常' |
| 检查 | 满足条件 | age INT CHECK (age>=0 AND age<=150) |
| 外键 | 引用主表主键 | FOREIGN KEY (user_id) REFERENCES users(id) |
添加/删除: 主键、唯一、外键、CHECK 用 ALTER TABLE … ADD / DROP;NOT NULL、DEFAULT 用 ALTER TABLE … MODIFY COLUMN …。
把约束类型、写法和常见错误过一遍后,你就能在设计表时合理使用主键、唯一、非空、默认、检查和外键,保证数据正确、一致。建议在本地建一两张带各种约束的表,故意插入违反约束的数据,观察报错,加深印象。