在数据库设计中,数据质量决定了系统的可靠性 。虽然数据类型(如 int、varchar)已经对字段值做了基本限制,但这远远不够满足复杂的业务需求。比如:用户的邮箱不能重复、学生的姓名不能为空、订单关联的客户必须真实存在...... 这些业务规则都需要通过MySQL 表约束来强制执行。
本文将从理论原理出发,结合可直接复制执行的实战实验,系统讲解 MySQL 中 8 种核心约束的作用、语法和最佳实践,帮助你彻底掌握如何设计健壮的数据库表结构。
1:表约束的核心理论
1:什么是表约束
表约束是数据库层面强制执行的数据完整性规则,它在数据插入、更新、删除时自动验证数据是否符合业务逻辑。如果违反约束,MySQL 会直接拒绝操作并返回错误,从源头阻止脏数据进入数据库。
数据完整性分为四个维度,不同的约束对应不同的完整性保障:
- 实体完整性:保证每条记录都是唯一可识别的(主键约束)
- 域完整性:保证字段值符合特定的数据类型和范围(NOT NULL、DEFAULT、CHECK)
- 参照完整性:保证表与表之间关联关系的正确性(外键约束)
- 用户定义完整性:满足特定业务规则的自定义约束(唯一键、枚举等)
2:MySQL常用约束分类
MySQL 支持多种约束类型,本文重点讲解最常用的 8 种:
| 约束类型 | 英文关键字 | 保障的完整性 | 核心作用 |
|---|---|---|---|
| 非空约束 | NOT NULL |
域完整性 | 禁止字段值为 NULL |
| 默认值约束 | DEFAULT |
域完整性 | 字段未赋值时使用默认值 |
| 注释约束 | COMMENT |
- | 字段说明,提高代码可维护性 |
| 零填充约束 | ZEROFILL |
- | 数字类型显示时自动补零 |
| 主键约束 | PRIMARY KEY |
实体完整性 | 唯一标识每条记录,非空且唯一 |
| 自增长约束 | AUTO_INCREMENT |
- | 自动生成唯一的整数 ID |
| 唯一键约束 | UNIQUE KEY |
用户定义完整性 | 保证字段值唯一,允许为空 |
| 外键约束 | FOREIGN KEY |
参照完整性 | 建立表与表之间的关联关系 |
2:基础约束
1:非空约束(NOT NULL)
MySQL 字段默认允许为NULL,但NULL 是一个特殊的值,表示 "未知" 或 "不存在" ,它不等于空字符串'',也不等于 0。任何与 NULL 的运算结果都是 NULL,这会导致很多意想不到的问题。
例如:SELECT 1 + NULL;的结果是 NULL,而不是 1;SELECT NULL = NULL;的结果也是 NULL,而不是 TRUE。
因此,在实际开发中,除非有特殊理由,否则所有字段都应该设置为 NOT NULL。
sql
-- 实验1:验证NOT NULL约束的作用
CREATE DATABASE IF NOT EXISTS constraint_demo DEFAULT CHARSET utf8mb4;
USE constraint_demo;
-- 创建班级表,班级名和教室都不能为空
CREATE TABLE myclass (
class_name varchar(20) NOT NULL,
class_room varchar(10) NOT NULL
);
-- 查看表结构(Null列显示NO表示非空)
DESC myclass;
-- 正常插入数据
INSERT INTO myclass(class_name, class_room) VALUES ('C++大牛班', '301教室');
SELECT * FROM myclass;
-- 错误演示:省略class_room字段(违反NOT NULL约束)
INSERT INTO myclass(class_name) VALUES ('Java大神班');
-- 预期错误:ERROR 1364 (HY000): Field 'class_room' doesn't have a default value


2:默认值约束
当某个字段的值在大多数情况下都是固定值时,可以设置默认值。插入数据时如果省略该字段,MySQL 会自动使用默认值,这样既简化了插入操作,又避免了空值问题。
注意 :NOT NULL和DEFAULT一般不需要同时使用,因为DEFAULT已经保证了字段不会为空。
sql
-- 实验2:验证DEFAULT约束的生效规则
CREATE TABLE user (
name varchar(20) NOT NULL,
age tinyint unsigned DEFAULT 0, -- 年龄默认0岁(未填写时)
sex char(2) DEFAULT '男' -- 性别默认男
);
-- 查看表结构(Default列显示默认值)
DESC user;
-- 插入时省略age和sex字段,自动使用默认值
INSERT INTO user(name) VALUES ('张三');
SELECT * FROM user;
-- 预期结果:张三 | 0 | 男
-- 插入时指定字段值,会覆盖默认值
INSERT INTO user(name, age, sex) VALUES ('李四', 20, '女');
SELECT * FROM user;
-- 预期结果:李四 | 20 | 女


3:辅助约束
1:注释约束
COMMENT用于给字段和表添加注释,它不会影响数据的存储和查询,但对于团队协作和后期维护至关重要。一个没有注释的数据库表,几个月后连开发者自己都可能忘记每个字段的含义。
注释会保存在 MySQL 的系统表中,使用DESC命令无法查看,需要使用SHOW CREATE TABLE命令。
sql
-- 实验3:验证COMMENT的使用和查看方式
CREATE TABLE student (
id int PRIMARY KEY COMMENT '学生学号,全局唯一',
name varchar(20) NOT NULL COMMENT '学生姓名',
score int DEFAULT 0 COMMENT '期末考试成绩,满分100'
) COMMENT '学生信息表';
-- DESC无法查看注释
DESC student;
-- 使用SHOW CREATE TABLE查看完整建表语句(包含注释)
SHOW CREATE TABLE student\G

2:零填充约束(ZEROFILL)
ZEROFILL是 MySQL 特有的约束,仅适用于整数类型字段。当数字的位数小于设定的显示宽度时,会在前面自动补 0。
重要提醒 :ZEROFILL只影响显示效果,不改变实际存储的值 。例如,int(5) ZEROFILL类型的字段存储值 1 时,显示为 00001,但数据库内部实际存储的还是 1。
sql
-- 实验4:验证ZEROFILL仅影响显示效果
CREATE TABLE num_test (
a int(5) unsigned ZEROFILL, -- 显示宽度为5,不足补零
b int(5) unsigned -- 普通整数类型
);
-- 插入相同的数据
INSERT INTO num_test(a, b) VALUES (1, 1);
-- 查看显示效果差异
SELECT * FROM num_test;
-- 预期结果:a=00001,b=1
-- 验证实际存储值(使用hex函数查看十六进制表示)
SELECT a, HEX(a), b, HEX(b) FROM num_test;
-- 预期结果:HEX(a)=1,HEX(b)=1,说明实际存储值相同

4:核心约束
1:主键约束(PRIMARY KEY)
主键是表中唯一标识一条记录的字段或字段组合,它是数据库设计的核心。主键具有三个不可动摇的特性:
- 唯一性:主键值不能重复
- 非空性:主键值不能为 NULL
- 稳定性:主键值一旦插入,尽量不要修改
一张表最多只能有一个主键,但主键可以由多个字段组成,称为复合主键。
设计原则:建议使用与业务无关的自增长整数作为主键(代理主键),而不是使用业务字段(如身份证号、手机号)作为主键(自然主键)。因为业务字段可能会发生变化,而代理主键永远不会变。
sql
-- 实验5:验证主键的唯一性和非空性
CREATE TABLE course (
course_id int PRIMARY KEY COMMENT '课程编号(主键)',
course_name varchar(30) NOT NULL COMMENT '课程名称'
);
-- 正常插入数据
INSERT INTO course(course_id, course_name) VALUES (101, 'MySQL数据库');
INSERT INTO course(course_id, course_name) VALUES (102, 'Linux操作系统');
SELECT * FROM course;
-- 错误演示1:主键重复
INSERT INTO course(course_id, course_name) VALUES (101, 'Python编程');
-- 预期错误:ERROR 1062 (23000): Duplicate entry '101' for key 'PRIMARY'
-- 错误演示2:主键为空
INSERT INTO course(course_id, course_name) VALUES (NULL, 'C++程序设计');
-- 预期错误:ERROR 1048 (23000): Column 'course_id' cannot be null
-- 实验6:复合主键的使用
CREATE TABLE score (
student_id int COMMENT '学生学号',
course_id int COMMENT '课程编号',
score int DEFAULT 0 COMMENT '考试成绩',
PRIMARY KEY(student_id, course_id) -- 学号+课程号作为复合主键
);
-- 正常插入
INSERT INTO score(student_id, course_id, score) VALUES (1, 101, 90);
INSERT INTO score(student_id, course_id, score) VALUES (1, 102, 85);
-- 复合主键冲突演示(只有当两个字段都相同时才会冲突)
INSERT INTO score(student_id, course_id, score) VALUES (1, 101, 95);
-- 预期错误:ERROR 1062 (23000): Duplicate entry '1-101' for key 'PRIMARY'


2:自增长约束
AUTO_INCREMENT通常与主键搭配使用,当插入数据时不给主键赋值,MySQL 会自动从当前最大值 + 1 生成一个唯一的整数 ID。
自增长的三个必要条件:
- 字段必须是整数类型
- 字段必须是索引(主键或唯一键)
- 一张表最多只能有一个自增长字段

5:高级约束
1:唯一键约束(UNIQUE KEY)
唯一键用于保证业务字段的唯一性,它与主键的核心区别在于:
- 唯一键允许为空(可以插入多个空值,因为 NULL 不等于 NULL)
- 一张表可以有多个唯一键
使用场景:邮箱、手机号、身份证号等业务上需要唯一但可能为空的字段。
sql
-- 实验8:对比唯一键和主键的区别
CREATE TABLE customer (
id int PRIMARY KEY AUTO_INCREMENT COMMENT '客户编号(主键)',
name varchar(20) NOT NULL COMMENT '客户姓名',
email varchar(64) UNIQUE COMMENT '电子邮箱(唯一键)',
phone char(11) UNIQUE COMMENT '手机号码(唯一键)'
);
-- 正常插入
INSERT INTO customer(name, email, phone) VALUES ('张三', 'zhangsan@test.com', '13800138000');
-- 错误演示:邮箱重复
INSERT INTO customer(name, email, phone) VALUES ('李四', 'zhangsan@test.com', '13900139000');
-- 预期错误:ERROR 1062 (23000): Duplicate entry 'zhangsan@test.com' for key 'email'
-- 唯一键允许为空(可插入多个空值)
INSERT INTO customer(name, email, phone) VALUES ('王五', NULL, NULL);
INSERT INTO customer(name, email, phone) VALUES ('赵六', NULL, NULL);
SELECT * FROM customer;

2:外键约束(FOREIGN KEY)
外键用于建立主表(父表)和从表(子表)之间的关联关系,保证从表的外键值必须在主表的主键列中存在,或者为 NULL。
外键的两个必要条件:
- 主表必须有主键或唯一键约束
- 从表的外键字段数据类型必须与主表的主键字段完全一致
级联操作:外键支持级联删除和级联更新,当主表的主键值被删除或更新时,从表的外键值会自动同步。常用的级联选项有:
ON DELETE CASCADE:主表记录删除时,从表关联记录也删除ON UPDATE CASCADE:主表主键更新时,从表外键也更新ON DELETE SET NULL:主表记录删除时,从表外键设为 NULL
sql
-- 实验9:验证外键的关联约束效果
-- 1. 先创建主表(班级表),必须有主键
CREATE TABLE class (
class_id int PRIMARY KEY COMMENT '班级编号(主键)',
class_name varchar(30) NOT NULL COMMENT '班级名称'
);
-- 2. 创建从表(学生表),设置外键关联班级表
CREATE TABLE student (
student_id int PRIMARY KEY COMMENT '学生学号(主键)',
name varchar(20) NOT NULL COMMENT '学生姓名',
class_id int COMMENT '班级编号(外键)',
FOREIGN KEY (class_id) REFERENCES class(class_id)
ON DELETE SET NULL -- 班级删除时,学生的班级号设为NULL
ON UPDATE CASCADE -- 班级编号更新时,学生的班级号同步更新
);
-- 3. 必须先插入主表数据,再插入从表数据
INSERT INTO class(class_id, class_name) VALUES (1, '一班'), (2, '二班');
INSERT INTO student(student_id, name, class_id) VALUES (1001, '小明', 1);
INSERT INTO student(student_id, name, class_id) VALUES (1002, '小红', 2);
-- 4. 错误演示:插入主表不存在的class_id
INSERT INTO student(student_id, name, class_id) VALUES (1003, '小刚', 3);
-- 预期错误:ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails
-- 5. 允许插入class_id为NULL(未分配班级)
INSERT INTO student(student_id, name, class_id) VALUES (1003, '小刚', NULL);
SELECT * FROM student;
-- 6. 验证级联更新
UPDATE class SET class_id=10 WHERE class_id=1;
SELECT * FROM student;
-- 预期结果:小明的class_id变为10
-- 7. 验证级联删除
DELETE FROM class WHERE class_id=2;
SELECT * FROM student;
-- 预期结果:小红的class_id变为NULL


6:综合电商数据库设计
下面我们设计一个完整的电商商店数据库,包含商品、客户和订单三个表,综合运用所有约束类型。
1:数据库设计思路
- 商品表 (goods):存储商品信息,商品编号作为自增长主键
- 客户表 (customer):存储客户信息,客户编号作为自增长主键,邮箱和身份证号作为唯一键
- 订单表 (purchase):存储购买记录,订单号作为自增长主键,客户号和商品号作为外键分别关联客户表和商品表
2:完整SQL代码
sql
-- 创建数据库
CREATE DATABASE IF NOT EXISTS shop DEFAULT CHARSET utf8mb4;
USE shop;
-- 商品表
CREATE TABLE goods (
goods_id int PRIMARY KEY AUTO_INCREMENT COMMENT '商品编号',
goods_name varchar(32) NOT NULL COMMENT '商品名称',
unitprice int NOT NULL DEFAULT 0 COMMENT '单价(单位:分,避免浮点数精度问题)',
category varchar(12) COMMENT '商品分类',
provider varchar(64) NOT NULL COMMENT '供应商名称'
) COMMENT '商品信息表';
-- 客户表
CREATE TABLE customer (
customer_id int PRIMARY KEY AUTO_INCREMENT COMMENT '客户编号',
name varchar(32) NOT NULL COMMENT '客户姓名',
address varchar(256) COMMENT '收货地址',
email varchar(64) UNIQUE KEY COMMENT '电子邮箱',
sex enum('男','女') NOT NULL COMMENT '性别(枚举类型,只能是男或女)',
card_id char(18) UNIQUE KEY COMMENT '身份证号'
) COMMENT '客户信息表';
-- 订单表
CREATE TABLE purchase (
order_id int PRIMARY KEY AUTO_INCREMENT COMMENT '订单号',
customer_id int NOT NULL COMMENT '客户编号',
goods_id int NOT NULL COMMENT '商品编号',
nums int NOT NULL DEFAULT 1 COMMENT '购买数量',
create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT '下单时间',
FOREIGN KEY (customer_id) REFERENCES customer(customer_id) ON DELETE CASCADE,
FOREIGN KEY (goods_id) REFERENCES goods(goods_id) ON DELETE CASCADE
) COMMENT '订单信息表';
-- 测试数据插入
INSERT INTO goods(goods_name, unitprice, category, provider) VALUES
('iPhone 15', 599900, '手机', '苹果公司'),
('华为Mate 60', 699900, '手机', '华为技术有限公司');
INSERT INTO customer(name, address, email, sex, card_id) VALUES
('张三', '北京市朝阳区', 'zhangsan@example.com', '男', '110101199001011234'),
('李四', '上海市浦东新区', 'lisi@example.com', '女', '310101199505055678');
INSERT INTO purchase(customer_id, goods_id, nums) VALUES
(1, 1, 1),
(2, 2, 2);
-- 查询订单详情
SELECT
o.order_id,
c.name AS customer_name,
g.goods_name,
o.nums,
g.unitprice * o.nums AS total_price,
o.create_time
FROM purchase o
JOIN customer c ON o.customer_id = c.customer_id
JOIN goods g ON o.goods_id = g.goods_id;
7:实践常见误区
1:约束使用最佳实践
- 所有字段都应该添加 NOT NULL 约束,除非有明确的理由允许为空
- 使用自增长整数作为主键,避免使用业务字段作为主键
- 所有字段和表都应该添加 COMMENT 注释,提高代码可维护性
- 唯一键用于保证业务字段的唯一性,如邮箱、手机号、身份证号
- 外键谨慎使用:在高并发、大数据量场景下,外键会影响性能,可以考虑在应用层实现关联约束
- 避免使用复合主键:复合主键会增加查询复杂度,建议使用代理主键 + 唯一索引的方式
2:常见误区
-
误区 1 :
int(10)表示整数最多存储 10 位数字- 纠正:
int类型固定占用 4 字节,存储范围是 - 2147483648 到 2147483647。括号内的数字只有在配合ZEROFILL使用时才有意义,表示显示宽度。
- 纠正:
-
误区 2:唯一键不允许为空
- 纠正:唯一键允许为空,并且可以插入多个空值,因为 NULL 不等于 NULL。
-
误区 3:外键会自动创建索引
- 纠正:MySQL 会自动为主键创建索引,但不会自动为外键创建索引。为了提高关联查询性能,建议手动为外键字段创建索引。
-
误区 4 :
AUTO_INCREMENT是连续的- 纠正:自增长 ID 不保证连续。当插入失败、事务回滚或删除记录时,自增长 ID 会出现断层。
8:总结
表约束是数据库设计的基石,它能从源头保证数据的完整性和正确性。通过本文的理论讲解和实战实验,我们系统学习了 MySQL 中 8 种核心约束的用法。
NOT NULL:禁止空值,避免运算错误DEFAULT:设置默认值,简化插入操作COMMENT:添加注释,提高可维护性ZEROFILL:数字补零,美化显示效果PRIMARY KEY:唯一标识记录,保证实体完整性AUTO_INCREMENT:自动生成 ID,简化主键管理UNIQUE KEY:保证业务字段唯一性FOREIGN KEY:建立表间关联,保证参照完整性