其他约束:UNIQUE 与 CHECK
本教程共 46 篇 · 第 33 篇 · 更新于 2026-07-30 · 约 10 分钟阅读
33. 其他约束:UNIQUE 与 CHECK
本节目标:掌握 UNIQUE、NOT NULL、DEFAULT、CHECK 四种约束的写法和作用,理解 CHECK 约束的版本差异,学会查看表上的约束信息。
前面讲了主键和外键,这一节把剩下的四种约束一次讲完。它们各自管一摊事,组合起来就能把数据质量卡住。
33.1 UNIQUE 约束
UNIQUE 约束 保证某列(或列组合)的值不重复。和主键的区别是:UNIQUE 允许 NULL,而且一张表可以有多个 UNIQUE。
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE,
age INT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
email VARCHAR(100) UNIQUE 让邮箱不能重复:
INSERT INTO users(username, email, age) VALUES('zhangsan', 'zhangsan@test.com', 25);
INSERT INTO users(username, email, age) VALUES('lisi', 'zhangsan@test.com', 28);
-- ERROR 1062 (23000): Duplicate entry 'zhangsan@test.com' for key 'email'
UNIQUE 和主键的区别
| 特性 | PRIMARY KEY | UNIQUE |
|---|---|---|
| 唯一性 | 是 | 是 |
| 允许 NULL | 否 | 是(可以有多个 NULL) |
| 每张表数量 | 最多 1 个 | 多个 |
| 自动建索引 | 聚簇索引 | 二级索引 |
NoteMySQL 中 UNIQUE 列允许有多个 NULL 值。这是因为 NULL 在 SQL 里代表”未知”,两个未知不算重复。这一点和 SQL Server 等数据库不同。
复合 UNIQUE
多列组合不重复,用 CONSTRAINT ... UNIQUE(列1, 列2):
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100),
age INT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uk_username_email UNIQUE(username, email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO users(username, email) VALUES('zhangsan', 'a@test.com'); -- OK
INSERT INTO users(username, email) VALUES('zhangsan', 'b@test.com'); -- OK,email 不同
INSERT INTO users(username, email) VALUES('zhangsan', 'a@test.com'); -- 报错,组合重复
建表后添加和删除 UNIQUE
-- 添加
ALTER TABLE users ADD CONSTRAINT uk_email UNIQUE(email);
-- 删除(需要先查出约束名)
SHOW INDEX FROM users;
ALTER TABLE users DROP INDEX uk_email;
TipUNIQUE 约束本质上就是建了一个唯一索引。所以查看和删除 UNIQUE 用的都是索引相关的操作(
SHOW INDEX、DROP INDEX)。
33.2 NOT NULL 约束
NOT NULL 约束 最简单:这一列不能存 NULL。
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL, -- 必填
email VARCHAR(100), -- 可空
age INT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO users(username, email) VALUES(NULL, 'test@test.com');
-- ERROR 1048 (23000): Column 'username' cannot be null
建表后修改
-- 允许为空
ALTER TABLE users MODIFY username VARCHAR(50) NULL;
-- 改为非空
ALTER TABLE users MODIFY username VARCHAR(50) NOT NULL;
Warning把已有数据的列改为 NOT NULL 之前,先确认该列没有 NULL 值,否则会报错。可以先执行
UPDATE users SET username = 'unknown' WHERE username IS NULL;填充默认值。
33.3 DEFAULT 约束
DEFAULT 约束 指定列的默认值。插入数据时如果不给这列赋值,就用默认值。
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100),
age INT DEFAULT 18,
status VARCHAR(20) DEFAULT 'active',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 不指定 age 和 status,用默认值
INSERT INTO users(username, email) VALUES('zhangsan', 'zhangsan@test.com');
SELECT age, status, created_at FROM users WHERE username = 'zhangsan';
-- 结果:18 | active | 2026-07-30 12:00:00
常用默认值写法
-- 数值默认值
age INT DEFAULT 0,
-- 字符串默认值
status VARCHAR(20) DEFAULT 'active',
-- 当前时间
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
-- 更新时自动刷新时间
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP 是个很实用的写法:每次 UPDATE 这行数据时,updated_at 自动更新为当前时间。
修改默认值
ALTER TABLE users ALTER COLUMN age SET DEFAULT 20;
-- 或者用 MODIFY
ALTER TABLE users MODIFY age INT DEFAULT 20;
-- 删除默认值
ALTER TABLE users ALTER COLUMN age DROP DEFAULT;
33.4 CHECK 约束
CHECK 约束 用来限制列值必须满足某个条件。比如年龄不能为负、订单金额必须大于零。
语法
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100),
age INT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_age CHECK (age >= 0 AND age <= 150)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO users(username, email, age) VALUES('zhangsan', 'zhangsan@test.com', 25); -- OK
INSERT INTO users(username, email, age) VALUES('lisi', 'lisi@test.com', -5);
-- ERROR 3819 (HY000): Check constraint 'chk_age' is violated.
也可以写成列级约束:
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT,
amount DECIMAL(10,2) CHECK (amount > 0),
status VARCHAR(20) DEFAULT 'pending',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
版本差异(重要)
| 版本 | CHECK 约束行为 |
|---|---|
| MySQL 5.7 及更早 | 语法能写但不生效,被静默忽略 |
| MySQL 8.0.15 | 语法能写但不生效,被静默忽略 |
| MySQL 8.0.16+ | 真正生效,违反时报错 |
| MySQL 26.7.0 | 生效 |
Warning如果你用的 MySQL 版本低于 8.0.16,CHECK 约束写了也白写,数据库不会帮你校验。要么升级到 8.0.16 以上,要么在应用层做校验。本教程基线版本 26.7.0 已完全支持 CHECK 约束。
查看和删除 CHECK 约束
-- 查看
SELECT * FROM information_schema.CHECK_CONSTRAINTS
WHERE TABLE_NAME = 'users';
-- 删除
ALTER TABLE users DROP CHECK chk_age;
33.5 约束查看汇总
想看一张表上所有约束,有几种方式:
-- 查看建表语句(最直观)
SHOW CREATE TABLE users\G
-- 查看索引(主键和UNIQUE)
SHOW INDEX FROM users;
-- 查看 CHECK 约束
SELECT * FROM information_schema.CHECK_CONSTRAINTS WHERE TABLE_NAME = 'users';
-- 查看外键
SELECT * FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_NAME = 'orders' AND REFERENCED_TABLE_NAME IS NOT NULL;
33.6 六大约束速查表
| 约束 | 关键字 | 作用 | 允许 NULL |
|---|---|---|---|
| 主键 | PRIMARY KEY | 唯一标识,非空 | 否 |
| 外键 | FOREIGN KEY | 引用另一表,保参照完整性 | 是 |
| 唯一 | UNIQUE | 值不重复 | 是 |
| 非空 | NOT NULL | 不允许 NULL | - |
| 默认值 | DEFAULT | 不赋值时用默认值 | - |
| 检查 | CHECK | 满足指定条件 | - |
33.7 小结
- UNIQUE 保证值不重复,允许 NULL,一张表可多个;
- NOT NULL 禁止 NULL,最简单也最常用;
- DEFAULT 指定默认值,
CURRENT_TIMESTAMP常用于时间列; - CHECK 约束限制列值范围,8.0.16 起才真正生效,低版本被忽略;
- 六大约束各有侧重,组合使用才能保证数据质量。
下一节进入视图(VIEW),学习如何把复杂查询”存”成一个虚拟表。