1.1 概念
1.1.1为什么需要约束
数据类型本身也是一种约束,但是数据类型的限制比较单一,我们还需要额外的约束规则,从业务逻辑层面保障数据合法、正确。 比如身份证字段,除了字符类型限制,还要求整条记录里面身份证不能重复,单纯依靠字段类型无法实现这类需求。
字段类型无法覆盖的需求:
- 字段是否允许为 NULL
- 字段是否可以设置默认值
- 给字段增加说明注释
- 数字字段的业务范围限制
- 设置字段作为行的唯一标识
MySQL 是数据存储方案,除了存放数据,还要尽可能保障数据安全,降低人为误操作带来的脏数据。
补充:约束不只是数据库层面可以实现,后端业务代码里面同样可以增加判断校验。数据库约束是底层兜底方案。
1.1.2约束是什么
约束是在数据表上强制执行的数据校验规则。本质是 MySQL 通过限制用户的增删改操作,维护数据安全与数据完整性。
数据完整性分为四类:
- 实体完整性:同一张表中,不能存在两条完全一样、无法区分的记录。
- 域完整性:限定字段取值范围,例如年龄 0~120,性别只能是男 / 女。
- 引用完整性:关联表之间的数据合法性,例如员工的部门编号,在部门表中必须存在。
- 用户自定义完整性:根据业务自定义规则,如用户名唯一、密码非空;部门经理薪资不能高于本部门普通员工平均工资的 5 倍。
约束属于数据库对象,保存在系统表中,拥有独立的约束名称。
MySQL 常用约束类型:
- 非空约束
NOT NULL - 默认值约束
DEFAULT - 主键约束
PRIMARY KEY - 外键约束
FOREIGN KEY - 唯一约束
UNIQUE - 检查约束
CHECK
约束创建时机:
- 创建表的时候同步定义约束
- 表创建完成之后,修改表结构追加约束
约束分为列级约束、表级约束:
- 列级约束:写在字段定义的后面,只对当前这一列生效
- 表级约束:所有字段定义完成之后再声明,可以支持多字段联合约束
1.2 语法
列级约束语法:在定义列的同时定义约束
create table 表名(列名 类型 约束);
表级约束语法:所有列定义完毕后定义约束
create table 表名 (
列名1 类型,
列名2 类型,
constraint 约束名 约束类型(列信息)
);
建表之后新增约束通用语法:
alter table 表名 add constraint 约束名 约束类型(要约束的列名);
约束命名推荐规范:表名_列名_约束类型简写,方便后期维护、查找、删除约束。
1.3 非空约束 NOT NULL
作用:限定某个字段的值不允许为空 。 字段有两种状态:NULL(默认,允许为空)、NOT NULL(不允许为空)。
默认情况下字段允许为空。开发中核心业务字段尽量设置非空,因为 NULL 值无法正常参与运算。
重点区分:所有数据类型都可以存 NULL;空字符串 ''、数字 0 都不等于 NULL。
业务场景举例:班级表包含班级名称、教室名称。班级名称必须填写,没有班级名这条记录没有意义;教室名称可以暂时留空,表示教室还未分配。这类需求就需要非空约束实现。
建表示例:
create table class (
id int,
class_name varchar(30) not null,
room varchar(30)
);
建表之后修改非空约束
-- change方式,字段名需要写两次
alter table student1 change column id id int not null;
-- modify方式(更推荐)
alter table student1 modify name varchar(30) not null;
说明:modify 语句中如果省略
not null,代表该字段恢复为允许为空。
⚠️ 注意:修改约束前要检查表内已有数据,如果该字段已经存在 NULL,直接设置 NOT NULL 会报错,修改失败。
删除非空约束(改为允许为空)
alter table student1 modify id int null;
-- 简写写法
alter table student1 modify name varchar(30);
1.4 默认值约束 DEFAULT
作用:给字段预先设置默认值。插入数据时,如果不给这个字段显式赋值,自动填充默认值。 生效条件:插入语句没有给该字段赋值。
注意:同一个字段同时设置
not null + default,not null 不会拦截插入;不赋值自动使用默认值,此时 not null 约束意义不大。
示例:
create table test2(
id int not null,
name varchar(30) not null,
sex char(2) default '女' not null
);
插入数据时不指定 sex 字段,sex 会自动填充默认值 女。
建表后追加默认值 场景:建表时 age 没有设置默认值,业务中新录入学员大部分年龄都是 18 岁,后期追加默认值
alter table test3 modify age int unsigned default 18;
1.5 列描述 ------ COMMENT
comment 是字段注释,没有校验约束功能,只用来描述字段含义,相当于 C/C++ 里面的注释。 注释信息会保存在建表语句中,但是 desc 表名 无法查看注释,必须使用 show create table 表名 查看完整建表语句。
示例:
create table user_info (
id int primary key comment '用户编号',
id_card varchar(18) comment '身份证号码'
);
1.6 主键约束 PRIMARY KEY
数据库除了存储数据,还需要快速检索数据。主键就是一条记录的唯一标识,类似学号、编号,可以通过主键精准定位一条记录。
主键特点:
- 主键字段不能为空、不能重复
- 一张表最多只能有一个主键
- 主键字段通常选用整型
- 主键是项目开发中使用最频繁的约束
补充:创建主键的时候,MySQL 会自动为主键建立索引,基于主键查询速度更快;删除主键约束,对应的主键索引也会同步删除。业务上尽量不要修改主键的值,主键是行的唯一标识,修改主键容易破坏数据完整性。
建表时添加主键(列级)
create table test5(id int primary key, name varchar(30) not null);
主键不能重复、不能为空,违反规则插入直接报错。
建表之后追加主键
alter table test2 add primary key(id);
⚠️ 注意:如果目标字段里面已经存在重复数据,无法添加主键,需要先清理重复数据。
删除主键
alter table test2 drop primary key;
复合主键(联合主键,表级约束) 多个字段组合在一起作为主键,组合整体不能重复,单独一列的值允许重复。适合 IP + 端口这类场景:
create table test6(
id int,
hostname varchar(10),
ip varchar(20),
port int unsigned,
primary key(ip,port)
);
1.7 主键自增长 AUTO_INCREMENT
作用:搭配主键使用,实现字段自动递增。不给该字段赋值时,系统读取当前字段最大值,+1 生成新值。
自增长约束硬性要求:
- 自增长字段必须是整数类型
- 字段必须是主键或者唯一键(字段具备唯一性,key 列有索引)
- 一张表里面最多只能有一个自增长字段
- 自增长字段隐含 NOT NULL
- 支持手动插入指定值,只要不和已有主键冲突即可;后续自增从表里最大值继续递增
先给字段添加主键,再开启自增长
alter table test2 add primary key(id);
alter table test2 modify id int auto_increment;
测试插入,不指定 id,自动递增:
insert into test2(name,sex) values('张三','男');
自定义自增长起始值
create table test7(
id int primary key auto_increment,
name varchar(30) not null
) auto_increment=100;
注意:自增起始参数写在括号外面。设置完成后,第一条自动生成的数据从 100 开始。
⚠️ 坑点:自增计数器,无论插入成功还是失败,计数器都会 + 1,会出现自增断号现象。
1.8 唯一性约束 UNIQUE
主键一张表只能有一个,当业务中有多个字段都要求不能重复时,使用唯一约束。
作用:限制字段的值不能重复。
主键和唯一键的关系:
- 主键:标识整条记录唯一性,用于检索,一张表只能一个,不允许 NULL
- 唯一键:满足业务字段唯一性,一张表可以设置多个,允许 NULL
- 二者是互补关系,不是对立关系,共同保证数据完整性
业务举例:学生表,id 作为主键用来检索;身份证号不能重复,设置为唯一约束。
建表示例:
create table test8(id int primary key, name varchar(30) unique);
本例中 name 不能重复,但是可以写入 NULL。
1.9 外键约束 FOREIGN KEY
作用:维护表与表之间的引用完整性。
概念说明:
- 主表(父表):被引用、被参考的表
- 从表(子表):引用主表数据的表
- 外键:从表中引用主表的字段
- 引用键:主表上被引用的字段
外键定义在从表上;主表被引用的字段必须是主键或者唯一键。 规则:从表外键的值,要么在主表引用字段中存在,要么是 NULL。
语法:
foreign key (从表的字段名称) references 主表名字(主表的字段名称)
业务示例:班级表为主表,学生表为从表,学生表 class_id 关联班级表 id。 新增学生的 class_id=30,前提是班级表中已经存在 id=30 的班级;想要删除班级表 id=20 的班级,要先删掉所有 class_id=20 的学生记录;class_id 可以填 NULL,代表学生暂未分配班级。
外键约束注意事项:
- 主表必须提前存在,或者和从表同时创建;同一张表引用自身叫做自参照表
- 主表被引用列必须是主键或者候选键
- 主键不能为 NULL,但是外键允许为 NULL
- 外键字段数量、数据类型,必须和主表引用字段保持一致
实操示例
-- 定义主表:班级表
create table myclass(id int primary key, class_name varchar(20) not null);
-- 定义从表:学生表
create table myclass_stu(
id int primary key,
name varchar(30) not null,
class_id int,
foreign key(class_id) references myclass(id)
);
-- 主表插入班级数据
insert into myclass values(1,'C++'),(2,'JAVA');
-- 正常插入学生
insert into myclass_stu values(1,'Li',1),(2,'Sun',2);
-- 报错:主表不存在id=3的班级,外键拦截
insert into myclass_stu values(3,'Zhang',3);
-- class_id为NULL,允许插入,表示未分配班级
insert into myclass_stu values(3,'Zhang',null);
1.10 检查约束 CHECK
作用:校验字段的值是否符合业务条件,用来限定取值范围,拦截无效数据。
理解:NOT NULL、DEFAULT 可以看作是特殊的检查约束。
版本重点:MySQL8.0.16 之前,写 CHECK 语法不会报错,但是约束不生效,只能通过触发器实现;8.0.16 版本及以后正式支持 CHECK 约束。
示例:薪资大于 0,小于 15000
create table test1(
id int primary key,
name varchar(30) not null,
salary float,
check(salary>0 and salary<15000)
);
insert into test1 values(1,'Li',10000.5);
-- 超出范围,插入失败
insert into test1 values(2,'Sun',17000);
1.11 删除表的约束
-- 删除非空约束
alter table 表名 modify 列名 类型;
-- 删除唯一约束
alter table 表名 drop index 惟一约束名;
-- 删除主键约束
alter table 表名 drop primary key;
-- 删除外键约束
alter table 表名 drop foreign key 外键名;