MySQL 表约束全解:从基础约束到外键关联规则

🔥小叶-duck个人主页

❄️个人专栏《Data-Structure-Learning》《C++入门到进阶&自我学习过程记录》

《Linux系统从入门到实践》《Linux网络从入门到实践》

《Qt 方寸极境》 《MySQL》

未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游


目录

前言

一、数据表约束:基础定义与核心作用解析

二、基础字段约束:空值控制、默认值与附加属性

[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 核心知识点展开讲解,夯实数据库底层开发能力。

相关推荐
ZCBUS实时计算1 小时前
金融证券实时数仓建设实践:轻量化实时计算平台落地,实现交易数据端到端秒级处理
大数据·数据库·数据仓库·金融·flink·dba·etl
zd2005721 小时前
海洋微生物数据库
数据库·宏基因组
海上小飞龙1 小时前
Redis 分布式锁原理:从 SET NX EX 到 Redisson 看门狗
数据库·redis·分布式
Hammer_Hans2 小时前
DFT笔记98
java·开发语言·数据库
迪康Defender3 小时前
从静态存储到动态流转:终端透明加密两种模式实战解析
运维·服务器·网络·数据库·其他
2601_965798473 小时前
Is Piroll WordPress Theme Worth It for Freelancers? Full Review
数据库·php
冷凝娇3 小时前
【MySQL】2026总结(二)
数据库·mysql
aaa小葵5 小时前
LangGraph
数据库·人工智能
wWYy.5 小时前
Mysql:Read View
数据库·mysql