Mysql

关注公众号 jb51net

关闭
首页 > 数据库 > Mysql > MySQL数据库表约束

MySQL数据库表约束的核心概念与应用实践

作者:小叶-duck

在 MySQL 数据库架构设计里,数据类型决定了字段底层的存储规则与占用空间,而各类表约束则是从业务逻辑层面筑牢防线,保障数据完整合规,本文会逐一拆解 MySQL 常用表约束,搭配实操案例讲解语法用法、不同约束的差异以及日常开发高频踩坑点,需要的朋友可以参考下

前言

在 MySQL 数据库架构设计里,数据类型决定了字段底层的存储规则与占用空间,而各类表约束则是从业务逻辑层面筑牢防线,保障数据完整合规。倘若建表时不配置约束,很容易滋生无效空值、重复冗余数据、数据表关联错乱等问题,比如学生归属的班级编号不存在、账号重复注册等异常场景。

合理搭配约束可以让数据库自主完成合法性校验,大幅削减后端代码里繁琐的数据判断逻辑。本文会逐一拆解 MySQL 常用表约束,搭配实操案例讲解语法用法、不同约束的差异以及日常开发高频踩坑点,带领大家搭建稳定、规范、健壮的数据表结构。

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

表约束是对表中字段的规则限制,用于保证数据的准确性、唯一性和关联性。MySQL 支持的核心约束包括:

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

基础约束主要控制字段的空值和默认值,是表设计的基础要求。

2.1 空属性约束(NULL/NOT NULL)

实战案例:
创建班级表,要求班级名和教室不能为空:

-- 创建表(班级名和教室非空)
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,性别默认 “男”:

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 是没手动赋值时自动填充默认值,二者目标一致、互相配合,不存在矛盾,属于互补关系。
 互补逻辑:

2.3 列描述(COMMENT)

COMMENT用于描述字段含义,无实际校验作用,仅方便开发者和 DBA 理解表结构,需通过SHOW CREATE TABLE查看。

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表的建表语句:

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列就是前面 插入的数据,如下:

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表的属性:

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属性,再进行查找,返回如下结果:

mysql> select * from tt3;
+-------+------+
| a     | b    |
+-------+------+
| 00001 |    2 |
+-------+------+

这次可以看到a的值由原来的1变成00001,这就是 zerofill 属性的作用:
如果宽度小于设定的宽度(这里设置的是5),自动填充0。
要注意的是,这只是最后显示的结果,在MySQL中实际存储的还是1。为什么是这样呢?我们可以用hex函数来证明:

mysql> select a, hex(a) from tt3;
+-------+--------+
| a     | hex(a) |
+-------+--------+
| 00001 | 1      |
+-------+--------+

可以看出数据库内部存储的还是1,00001只是设置了zerofill属性后的一种格式化输出而已。

三、唯一性约束:主键、自增与唯一索引详解

主键、自增长、唯一键是​保证数据唯一性的核心约束,解决 “重复数据” 和 “逻辑主键” 问题。 ​

3.1 主键约束(PRIMARY KEY)

主键是表的 “唯一标识”,核心特性:

实战案例 1:单字段主键

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    |       |
+-------+------------------+------+-----+---------+-------+
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'

alter table 表名 add primary key(字段列表)
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:复合主键(多字段联合主键)
当单个字段无法唯一标识记录时,可使用复合主键(多字段联合唯一):

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生成新值,核心特性:

实战案例:

-- 主键+自增长(逻辑主键)
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 个多个
核心作用标识唯一记录保证业务字段不重复

实战案例:
创建学生表,学号唯一(允许为空):

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 外键核心规则

4.2 实战案例

创建班级表(主表)和学生表(从表),学生表的 class_id 关联班级表的 id:

-- 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 图例理解外键的意义

五、综合落地实战:电商订单数据表完整设计

结合所有约束,设计电商系统的商品表、客户表、购买表,满足以下需求:

商品表:

-- 创建商品数据表,按需优化字段类型与约束
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 '供应商名称'
);

客户表:

-- 创建客户信息表
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 '身份证'
);

购买表:

-- 购买订单表,记录客户与商品的下单关联,使用外键绑定客户表、商品表
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)
);

六、约束选用避坑要点与全文总结

结束语

本文从 MySQL 约束的基础概念出发,循序渐进讲解了空值、默认值等基础字段约束,深入剖析了主键、自增、唯一键这类保证数据唯一性的核心约束,又对外键的联动规则、实战用法与使用利弊做了细致拆解,最后结合电商订单表完成综合落地练习,梳理了各类约束的常见踩坑点与选型方案。

各类表约束本质上是数据库自带的数据校验规则,合理使用约束,能够在数据库层面拦截脏数据、保证数据完整统一,大幅减轻后端代码的校验压力;但约束并非越多越好,外键等强约束会带来锁开销、耦合过高等问题,在高并发业务中需要理性取舍。

建表是数据库开发的起点,约束设计直接决定数据表长期稳定性。熟练区分各类约束的适用场景,结合业务灵活搭配约束规则,养成规范建表的习惯,能从源头减少后期数据异常、SQL 故障等问题。后续我们会继续围绕索引、事务等 MySQL 核心知识点展开讲解,夯实数据库底层开发能力。

以上就是MySQL数据库表约束的核心概念与应用实践的详细内容,更多关于MySQL数据库表约束的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文