
🔥小叶-duck :个人主页
❄️个人专栏:《Data-Structure-Learning》《C++入门到进阶&自我学习过程记录》
《Linux系统从入门到实践》《Linux网络从入门到实践》
✨未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游
目录
[2.1 空属性约束(NULL/NOT NULL)](#2.1 空属性约束(NULL/NOT NULL))
[2.2 默认值约束(DEFAULT)](#2.2 默认值约束(DEFAULT))
[2.3 列描述(COMMENT)](#2.3 列描述(COMMENT))
[2.4 零填充约束(ZEROFILL)](#2.4 零填充约束(ZEROFILL))
[3.1 主键约束(PRIMARY KEY)](#3.1 主键约束(PRIMARY KEY))
[3.2 自增长约束(AUTO_INCREMENT)](#3.2 自增长约束(AUTO_INCREMENT))
[3.3 唯一键约束(UNIQUE KEY)](#3.3 唯一键约束(UNIQUE KEY))
[四、表关联约束:外键 FOREIGN KEY 深度讲解](#四、表关联约束:外键 FOREIGN KEY 深度讲解)
[4.1 外键核心规则](#4.1 外键核心规则)
[4.2 实战案例](#4.2 实战案例)
[4.3 图例理解外键的意义](#4.3 图例理解外键的意义)
前言
在 MySQL 数据库架构设计里,数据类型决定了字段底层的存储规则与占用空间,而各类表约束则是从业务逻辑层面筑牢防线,保障数据完整合规。倘若建表时不配置约束,很容易滋生无效空值、重复冗余数据、数据表关联错乱等问题,比如学生归属的班级编号不存在、账号重复注册等异常场景。
合理搭配约束可以让数据库自主完成合法性校验,大幅削减后端代码里繁琐的数据判断逻辑。本文会逐一拆解 MySQL 常用表约束,搭配实操案例讲解语法用法、不同约束的差异以及日常开发高频踩坑点,带领大家搭建稳定、规范、健壮的数据表结构。
一、数据表约束:基础定义与核心作用解析
表约束是对表中字段的规则限制 ,用于保证数据的准确性、唯一性和关联性。MySQL 支持的核心约束包括:
- **空属性约束(NULL/NOT NULL):**限制字段是否允许为空;
- **默认值约束(DEFAULT):**字段未赋值时自动使用默认值;
- **列描述(COMMENT):**字段说明(无校验作用);
- **零填充约束(ZEROFILL):**数字类型不足指定宽度时填充 0;
- **主键约束(PRIMARY KEY):**唯一标识记录,非空且唯一;
- **自增长约束(AUTO_INCREMENT):**整数字段自动递增;
- **唯一键约束(UNIQUE KEY):**字段值唯一,允许为空;
- **外键约束(FOREIGN KEY):**关联两张表,保证数据逻辑一致性。
二、基础字段约束:空值控制、默认值与附加属性
基础约束主要控制字段的空值和默认值,是表设计的基础要求。
2.1 空属性约束(NULL/NOT NULL)
- NULL :默认值,字段允许为空(空值无法参与运算,如1+NULL=NULL);
- NOT NULL :字段不允许为空,插入 / 更新时必须赋值。
实战案例:
创建班级表,要求班级名和教室不能为空:
sql
-- 创建表(班级名和教室非空)
CREATE TABLE myclass(
class_name VARCHAR(20) NOT NULL,
class_room VARCHAR(10) NOT NULL
);
-- 插入合法数据(成功)
INSERT INTO myclass VALUES('class1', '301');
-- 插入非法数据(未给class_room赋值,报错)
INSERT INTO myclass(class_name) VALUES('class2');
-- 报错:ERROR 1364 (HY000): Field 'class_room' doesn't have a default value

2.2 默认值约束(DEFAULT)
当字段经常性出现某个固定值时,可设置DEFAULT ,插入时省略该字段 则自动使用默认值。
实战案例:
创建用户表,年龄默认 0,性别默认 "男":
sql
CREATE TABLE tt10(
name VARCHAR(20) NOT NULL, -- 非空(必须赋值)
age TINYINT UNSIGNED DEFAULT 0, -- 默认0
sex CHAR(2) DEFAULT '男' -- 默认男
);
-- 插入时仅赋值name,age和sex使用默认值
INSERT INTO tt10(name) VALUES('zhangsan');
-- 查询结果
SELECT * FROM tt10;
-- +----------+-----+-----+
-- | name | age | sex |
-- +----------+-----+-----+
-- | zhangsan | 0 | 男 |
-- +----------+-----+-----+

注意: NOT NULL 规定字段绝对不能存 NULL ;DEFAULT 是没手动赋值时自动填充默认值 ,二者目标一致、互相配合,不存在矛盾 ,属于互补关系 。
互补逻辑:
- 只写NOT NULL、不设默认值:插入时必须手动填值,漏写会报错;
- 只写默认值、无NOT NULL:可以手动插入 NULL,约束松散。
- **NOT NULL + DEFAULT:**漏写字段时,默认值自动补上,既满足非空要求,又不用每次手动填写,容错性更强;

2.3 列描述(COMMENT)
COMMENT用于描述字段含义,无实际校验作用,仅方便开发者和 DBA 理解表结构,需通过SHOW CREATE TABLE查看。
sql
CREATE TABLE tt12(
name VARCHAR(20) NOT NULL COMMENT '姓名',
age TINYINT UNSIGNED DEFAULT 0 COMMENT '年龄',
sex CHAR(2) DEFAULT '男' COMMENT '性别'
);
-- desc无法查看注释
DESC tt12;
-- 通过SHOW CREATE TABLE查看注释
SHOW CREATE TABLE tt12\G
-- 结果:
-- `name` varchar(20) NOT NULL COMMENT '姓名',
-- `age` tinyint(3) unsigned DEFAULT '0' COMMENT '年龄',
-- `sex` char(2) DEFAULT '男' COMMENT '性别'
2.4 零填充约束(ZEROFILL)
刚开始学习数据库时,很多人对数字类型后面的长度很迷茫。通过show看看tt3表的建表语句:
sql
mysql> show create table tt3\G
***************** 1. row *****************
Table: tt3
Create Table: CREATE TABLE `tt3` (
`a` int(10) unsigned DEFAULT NULL,
`b` int(10) unsigned DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=gbk
1 row in set (0.00 sec)
可以看到int(10) ,这个代表什么意思呢?整型不是4字节吗 ?这个10又代表什么 呢?其实没有zerofill 这个属性,括号内的数字是毫无意义的。a和b列就是前面插入的数据,如下:
sql
mysql> insert into tt3 values(1,2);
Query OK, 1 row affected (0.00 sec)
mysql> select * from tt3;
+------+------+
| a | b |
+------+------+
| 1 | 2 |
+------+------+
但是对列添加了zerofill属性后,显示的结果就有所不同了。修改tt3表的属性:
sql
mysql> alter table tt3 change a a int(5) unsigned zerofill;
mysql> show create table tt3\G
*************************** 1. row ***************************
Table: tt3
Create Table: CREATE TABLE `tt3` (
`a` int(5) unsigned zerofill DEFAULT NULL, --具有了zerofill
`b` int(10) unsigned DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=gbk
1 row in set (0.00 sec)
对a列添加了zerofill属性,再进行查找,返回如下结果:
sql
mysql> select * from tt3;
+-------+------+
| a | b |
+-------+------+
| 00001 | 2 |
+-------+------+
这次可以看到a的值由原来的1变成00001,这就是 zerofill 属性的作用:
如果宽度小于设定的宽度(这里设置的是5),自动填充0。 要注意的是,这只是最后显示的结果,在MySQL中实际存储的还是1。为什么是这样呢?我们可以用hex函数来证明:
sql
mysql> select a, hex(a) from tt3;
+-------+--------+
| a | hex(a) |
+-------+--------+
| 00001 | 1 |
+-------+--------+
可以看出数据库内部存储的还是1 ,00001只是设置了zerofill属性后的一种格式化输出而已。
- 关键:无ZEROFILL时,数字类型后的宽度(如INT(10))毫无意义,仅用于显示格式化。
三、唯一性约束:主键、自增与唯一索引详解
主键、自增长、唯一键 是保证数据唯一性的核心约束,解决 "重复数据" 和 "逻辑主键" 问题。
3.1 主键约束(PRIMARY KEY)
主键是表的 "唯一标识",核心特性:
- 非空(NOT NULL)且唯一(UNIQUE);
- 一张表最多只能有一个主键;
- 通常用于标识唯一记录(如用户 ID、学号)。
实战案例 1:单字段主键
- 创建表的时候直接在字段上指定主键
sql
mysql> create table tt13 (
-> id int unsigned primary key comment '学号不能为空',
-> name varchar(20) not null);
Query OK, 0 rows affected (0.00 sec)
mysql> desc tt13;
+-------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+-------+
| id | int(10) unsigned | NO | PRI | NULL | | <= key 中 pri表示该字段是主键
| name | varchar(20) | NO | | NULL | |
+-------+------------------+------+-----+---------+-------+
- 主键约束:主键对应的字段中不能重复,一旦重复,操作失败。
sql
mysql> insert into tt13 values(1, 'aaa');
Query OK, 1 row affected (0.00 sec)
mysql> insert into tt13 values(1, 'aaa');
ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'


- 当表创建好以后但是没有主键的时候,可以再次追加主键
sql
alter table 表名 add primary key(字段列表)
- 删除主键
sql
alter table 表名 drop primary key;
mysql> alter table tt13 drop primary key;
mysql> desc tt13;
+-------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+-------+
| id | int(10) unsigned | NO | | NULL | |
| name | varchar(20) | NO | | NULL | |
+-------+------------------+------+-----+---------+-------+

实战案例 2:复合主键(多字段联合主键)
当单个字段无法唯一标识记录时,可使用复合主键(多字段联合唯一):
sql
mysql> create table tt14(
-> id int unsigned,
-> course char(10) comment '课程代码',
-> score tinyint unsigned default 60 comment '成绩',
-> primary key(id, course) -- id和course为复合主键
-> );
Query OK, 0 rows affected (0.01 sec)
mysql> desc tt14;
+--------+---------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+---------------------+------+-----+---------+-------+
| id | int(10) unsigned | NO | PRI | 0 | | <= 这两列合成主键
| course | char(10) | NO | PRI | | |
| score | tinyint(3) unsigned | YES | | 60 | |
+--------+---------------------+------+-----+---------+-------+
mysql> insert into tt14 (id,course)values(1, '123');
Query OK, 1 row affected (0.02 sec)
mysql> insert into tt14 (id,course)values(1, '123');
ERROR 1062 (23000): Duplicate entry '1-123' for key 'PRIMARY' -- 主键冲突


3.2 自增长约束(AUTO_INCREMENT)
自增长 字段会自动从当前最大值 + 1生成新值,核心特性:
- 必须是整数类型;
- 必须是索引(KEY一栏有值,通常与主键搭配);
- 一张表最多只能有一个自增长。
实战案例:
sql
-- 主键+自增长(逻辑主键)
CREATE TABLE tt21(
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(10) NOT NULL DEFAULT ''
);
-- 插入时省略id,自动递增
INSERT INTO tt21(name) VALUES('a');
INSERT INTO tt21(name) VALUES('b');
-- 查询结果
SELECT * FROM tt21;
-- +----+------+
-- | id | name |
-- +----+------+
-- | 1 | a |
-- | 2 | b |
-- +----+------+
-- 获取上次插入的自增长值
SELECT LAST_INSERT_ID(); -- 结果:1(批量插入返回第一个值)


3.3 唯一键约束(UNIQUE KEY)
唯一键用于保证字段值唯一,但允许为空(空值不参与唯一性比较),解决 "一张表多个唯一字段" 的需求(主键仅能有一个)。
- 主键与唯一键的区别:
| 特性 | 主键(PRIMARY KEY) | 唯一键(UNIQUE KEY) |
|---|---|---|
| 唯一性 | 是 | 是 |
| 非空性 | 是 | 否(允许空值) |
| 一张表数量 | 最多 1 个 | 多个 |
| 核心作用 | 标识唯一记录 | 保证业务字段不重复 |
实战案例 :
创建学生表,学号唯一(允许为空):
sql
mysql> create table student (-> id char(10) unique comment '学号,不能重复,但可以为空',
-> name varchar(10)
-> );
Query OK, 0 rows affected (0.01 sec)
mysql> insert into student(id, name) values('01', 'aaa');
Query OK, 1 row affected (0.00 sec)
mysql> insert into student(id, name) values('01', 'bbb'); --唯一约束不能重复
ERROR 1062 (23000): Duplicate entry '01' for key 'id'
mysql> insert into student(id, name) values(null, 'bbb'); -- 但可以为空
Query OK, 1 row affected (0.00 sec)
mysql> select * from student;
+------+------+
| id | name |
+------+------+
| 01 | aaa |
| NULL | bbb |
+------+------+



四、表关联约束:外键 FOREIGN KEY 深度讲解
外键用于定义主表和从表的关联关系 , 保证数据的逻辑一致性(如学生的班级必须存在于班级表中)。
4.1 外键核心规则
- 外键 定义在从表 上,主表必须有主键或唯一键;
- 从表外键字段的值必须在主表对应字段中存在,或为NULL;
- 主表删除 / 修改关联记录时,需处理从表关联数据(如级联删除、拒绝操作)。
4.2 实战案例
创建班级表(主表)和学生表(从表),学生表的 class_id 关联班级表的 id:
sql
-- 1. 创建主表(班级表)
CREATE TABLE myclass(
id INT PRIMARY KEY, -- 主键
name VARCHAR(30) NOT NULL COMMENT '班级名'
);
-- 2. 创建从表(学生表),添加外键
CREATE TABLE stu(
id INT PRIMARY KEY,
name VARCHAR(30) NOT NULL COMMENT '学生名',
class_id INT,
-- 外键:class_id关联myclass的id
FOREIGN KEY (class_id) REFERENCES myclass(id)
);
-- 3. 插入主表数据
INSERT INTO myclass VALUES(10, 'C++大牛班'), (20, 'Java大神班');
-- 4. 插入合法从表数据(class_id在主表存在)
INSERT INTO stu VALUES(100, '张三', 10), (101, '李四', 20);
-- 5. 插入非法从表数据(class_id=30在主表不存在,报错)
INSERT INTO stu VALUES(102, '王五', 30);
-- 报错:ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails
-- 6. 插入class_id=NULL(未分配班级,成功)
INSERT INTO stu VALUES(102, '赵六', NULL);
4.3 图例理解外键的意义





五、综合落地实战:电商订单数据表完整设计
结合所有约束,设计电商系统的商品表、客户表、购买表,满足以下需求:
- 商品表:商品编号自增主键,名称非空,单价默认 0;
- 客户表:客户编号自增主键,姓名非空,邮箱唯一,性别枚举(男 / 女),身份证唯一;
- 购买表:订单号自增主键,关联客户编号和商品编号(外键),购买数量默认 0。
商品表:
sql
-- 创建商品数据表,按需优化字段类型与约束
create table if not exists goods
(
-- 主键自增,唯一商品编号
goods_id int primary key auto_increment comment '商品编号',
-- 商品名称非空约束
goods_name varchar(32) not null comment '商品名称',
-- 单价为float浮点型,解决int整数精度不足问题,非空默认0
unitprice float not null default 0 comment '单价,单位分',
-- 分类用ENUM固定枚举约束,非法值禁止插入;不传值默认填充【其他】
category enum('数码家电','服装鞋帽','食品生鲜','美妆护肤','图书文具','日用百货','其他') default '其他' comment '商品分类',
-- 供应商ENUM约束限定合法厂商,非法值拦截;not null强制必填,不允许空缺
provider enum('华为','小米','京东自营','拼多多商家','农夫山泉','欧莱雅','得力') not null comment '供应商名称'
);
客户表:
sql
-- 创建客户信息表
create table if not exists customer
(
-- 自增主键,唯一标识客户编号
customer_id int primary key auto_increment comment '客户编号',
-- 姓名非空约束,默认值匿名,未填写姓名自动填充匿名
name varchar(32) not null default '匿名' comment '客户姓名',
-- 收货地址,允许为空
address varchar(256) comment '客户地址',
-- 邮箱唯一约束,保证邮箱不重复
email varchar(64) unique key comment '电子邮箱',
-- 性别枚举限定男女,非空强制必填
sex enum('男','女') not null comment '性别',
-- 身份证固定18位字符,全局唯一不可重复
card_id char(18) unique key comment '身份证'
);
购买表:
sql
-- 购买订单表,记录客户与商品的下单关联,使用外键绑定客户表、商品表
create table if not exists purchase
(
order_id int primary key auto_increment comment '订单号,自增主键,唯一标识每一笔订单',
customer_id int comment '客户编号,关联客户表主键',
goods_id int comment '商品编号,关联商品表主键',
nums int default 0 comment '购买数量,未填写默认值为0',
-- 外键约束:客户编号必须在customer表中真实存在,保证数据合法性
foreign key (customer_id) references customer(customer_id),
-- 外键约束:商品编号必须在goods表中真实存在,防止无效商品下单
foreign key (goods_id) references goods(goods_id)
);
六、约束选用避坑要点与全文总结
- 优先使用非空约束:尽量让字段NOT NULL,空值会导致查询条件(如WHERE age=0)失效,且无法参与运算;
- 主键选择逻辑 ID:主键建议用与业务无关的自增整数(如 id),避免用身份证、手机号等业务字段(需频繁修改);
- 唯一键保证业务唯一性:如邮箱、身份证等业务字段,用UNIQUE KEY约束,而非主键;
- 外键谨慎使用:外键会降低表的插入 / 更新性能,高并发场景可去掉外键,在程序中校验关联性;
- 自增长字段注意重置: 删除表中数据后,自增长值不会自动重置,需用 ALTER TABLE 表名 AUTO_INCREMENT=1 手动重置。
结束语
本文从 MySQL 约束的基础概念出发,循序渐进讲解了空值、默认值等基础字段约束,深入剖析了主键、自增、唯一键这类保证数据唯一性的核心约束,又对外键的联动规则、实战用法与使用利弊做了细致拆解,最后结合电商订单表完成综合落地练习,梳理了各类约束的常见踩坑点与选型方案。
各类表约束本质上是数据库自带的数据校验规则,合理使用约束,能够在数据库层面拦截脏数据、保证数据完整统一,大幅减轻后端代码的校验压力;但约束并非越多越好,外键等强约束会带来锁开销、耦合过高等问题,在高并发业务中需要理性取舍。
建表是数据库开发的起点,约束设计直接决定数据表长期稳定性。熟练区分各类约束的适用场景,结合业务灵活搭配约束规则,养成规范建表的习惯,能从源头减少后期数据异常、SQL 故障等问题。后续我们会继续围绕索引、事务等 MySQL 核心知识点展开讲解,夯实数据库底层开发能力。