【MySQL】约束类型
约束详解
- 一、数据库约束的本质与价值
- 二、六大核心约束类型详解
- 1. NOT NULL:非空约束
- 2. UNIQUE:唯一约束
- 3. DEFAULT:默认值约束
- 4. PRIMARY KEY:主键约束
- 5. FOREIGN KEY:外键约束
- 6. CHECK:检查约束
- 三、约束的管理与维护
- 1. 查看表中的约束
- 2. 删除约束
- 3. 约束的性能影响
- 四、约束设计的最佳实践
- 1. 主键设计规范
- 2. 外键使用准则
- 3. 约束组合策略
- 4. 版本兼容性处理
- 五、总结
一、数据库约束的本质与价值
数据库约束是嵌入在表结构中的规则集合,用于限制存入表中的数据范围和关系,从而保证数据的完整性(Integrity)。这种完整性包含三个维度:
- 实体完整性:确保表中的每个记录都是唯一可识别的(如主键约束)
- 域完整性:保证列值符合特定的数据类型和格式要求(如非空约束、检查约束)
- 参照完整性:维护表之间关联关系的正确性(如外键约束)
人工校验数据完整性不仅效率低下,更难以应对高频次的数据操作。而数据库约束将这种校验工作"自动化"和"底层化",带来三大核心价值:
- 减少错误数据:在数据写入时即时拦截不符合规则的记录,从源头避免脏数据产生
- 降低开发成本:省去大量手动编写校验逻辑的工作,简化应用程序代码
- 保障数据一致性:即使多个应用程序同时操作数据库,也能通过约束维持数据规则的统一
约束通常与表结构一同定义,作用于特定列或多列组合。不同数据库系统对约束的支持存在细微差异,但核心类型和实现原理基本一致。
二、六大核心约束类型详解
1. NOT NULL:非空约束
定义:限制列不能存储NULL值,强制该字段必须有具体内容。
应用场景:所有必须填写的信息,如用户姓名、订单编号、身份证号等。在业务逻辑中,这些字段如果缺失会导致数据不完整。
创建方式:
-- 建表时添加非空约束
CREATE TABLE student (id INT NOT NULL,name VARCHAR(50) NOT NULL -- 用户名不能为空
);
行为特性:
- 插入记录时,必须为非空列提供具体值,否则会抛出
ERROR 1048 (23000): Column 'username' cannot be null
- 修改记录时,不能将非空列的值设置为NULL
- 可以通过
DESCRIBE
命令查看列的非空属性,"Null"列显示"NO"表示非空约束生效:
- 可以通过
DESCRIBE
命令查看列的非空属性,"Null"列显示"NO"表示非空约束生效
这个时候null列:
NO表示当前列不能为空
YES 表示当前列可以为空
注意事项:
- 非空约束是最基础的约束类型,应优先为关键业务字段添加
- 对于可选信息(如用户的昵称、备注)不应设置非空约束
- MySQL中,字符串类型的非空列如果未指定默认值,插入时必须显式提供值
2. UNIQUE:唯一约束
定义:保证列中每一条记录的值都是唯一的,不允许重复(NULL值除外)。
也就是某列的值在整个表中不能重复,比如身份证号,学号
应用场景:需要唯一标识但不作为主键的字段,如手机号、邮箱地址、工号等。这些字段需要唯一性但可能存在业务上的变更需求。
引例:
不加唯一约束的时候,可能出现了编号相同,但是人名不同的情况,不符合逻辑
创建方式:
-- 建表时添加唯一约束
CREATE TABLE employee (id INT unique, --学号唯一name VARCHAR(50)
);
第二次直接再去插入相同的id的记录,就会报错了,因为ID的列那就不唯一了
但是对于的NULL可以重复插入,不受唯一约束的影响,因为null与null两者判断是为假
行为特性:
- 插入重复值时会触发
ERROR 1062 (23000): Duplicate entry '13800138000' for key 'phone'
- NULL值可以多次插入,因为数据库中NULL不等于任何值(包括自身)
- 唯一约束会隐式创建索引,提升查询效率
- 联合唯一约束中,只有所有列的值都相同才判定为重复
3. DEFAULT:默认值约束
定义:当插入记录时未指定该列的值,数据库自动使用预设的默认值填充
应用场景:需要设置默认状态的字段,如用户注册时的默认角色、订单的默认状态、创建时间的自动填充等
当为某列设置了默认约束的时候,如果不给这个列指定值才会使用默认约束
创建方式:
-- 建表时添加默认值约束
CREATE TABLE student (id BIGINT UNIQUE,name VARCHAR(50) DEFAULT '无名氏'
);
inser into student (id,name) values (2,NULL);
这时仅指定了id,这是name列使用的默认值填充
且会发现序列号为2的时候,它的名字是NULL
虽然有指定的默认约束,但是当我们手动指定那个这一列的值为NULL时,插入的值依然是NULL,因为这个NULL 是我们自己手动指定的,也可以理解我们想要的值
行为特性:
- 用户显式指定值时,默认值被覆盖(用户的制定的优先级要高于默认约束)
- 对于自增列、时间戳列等特殊类型,默认值可以关联系统函数(如
CURRENT_TIMESTAMP
)
常见误区:
- 为非空列设置默认值可以避免插入时必须指定该字段的麻烦
- 不要为字符串类型的默认值使用单引号以外的符号(如双引号可能在不同数据库中表现不一致)
- 日期时间类型的默认值建议使用数据库函数(如
NOW()
)而非固定字符串
4. PRIMARY KEY:主键约束
定义:非空约束(NOT NULL)和唯一约束(UNIQUE)的组合,用于唯一标识表中的每条记录,有助于更容易更快速地找到表中的一个特定的记录
主键约束的列既是非空的也是唯一的
应用场景:每张表都应该有主键,用于记录的唯一标识和快速定位,如用户ID、商品ID、订单ID等。
示例:
-- 单字段主键
CREATE TABLE student (id INT PRIMARY KEY, -- 主键约束-- id INT NOT NULL UNIQUE 这种写完也是主键约束sn INT UNIQUE,name VARCHAR(20) NOT NULL,
);
把id的列识别为非空且唯一,这时候可以通过查看表结构
这个时候我们可以看到表结构中id列是键值情况为主键而不是UNI
所以是id列知道那个非空和唯一的约束,列被标示为了一个PRI,表示它是一个主键
我们可以看到写入数据之后的两种约束同时生效
主键约束帮我们校验了非空和唯一,这两个校验在写入数据的时候对效率是有一定影响的,但是比起不做校验来说,这个性能消耗还是可以承担的,并且主键对索引有着非常重要的作用,所以最好还是建议为每张表定义一个主键
-- 自增主键(最常用方式)
create table student(id bigint primary key auto_increment,sn int unique,name varchar(20) default '无名氏'
);
auto_increment 表示的是自增类型
作用是:让数据库帮我们去维护逐渐的增长,不用程序员自己去计算了,在插入的时候,先找到最大的值,然后在这个基础上加1,去生成一个新的值,做为新一个数据行的主键(id列)值
insert into student values (null,10001,'张三');
insert into student values (null,10002,'李四');
全列插入:
在写入数据时,不具体指定主键列的值,而是用NULL代替
当设置了自增主键之后,发现写入null时,也可以成功的插入数据;这里并不是说把这个null写入数据库,而是说让数据库帮我们去处理这个列的值(自增操作)
insert into student (sn,name) values (10003,'王五'),(10004,'赵六');
主键值可以指定值,只要不重复就可以
insert into student (id,sn,name) values (100,10100,'钱七');
-- 这个时候再去添加一条数据insert into student (sn,name) values (10101,'王二麻子');
但是我们通过发现:我们新添加的数据行的id是101
在最大值的基本上再去加1,那么主键值在数据表中所以有可能是不连续的
类似于雪花算法,后续会在数据库的深入学习会给大家进行讲解
一个表中不允许有两个主键
create table test(id bigint primary key,sn bigint primary key
);
错误:不能定义多个主键在一张数据表中
但是一个主键同时可以包含多个列(复合主键)
-- 复合主键(多字段组合)
create table stu1(id bigint,name varchar(20),mail varchar(50),primary key(id,name)
);
这个时候都id,name两列中的主键都是PRI
:表示当表定义了复合主键,在唯一教研室,只有复合主键中的所有的列都相同才被判定为相同
再次进行插入数据记录:
insert into stu1 values (1,'张三','zs@qq.com');
主键的值是定义复合主键时,多个列值的组合,复合主键一般用-进行连接,进而表示复合主键
这个时候名字和编号都相同,就会判定主键是相同的
insert into stu1 values (1,'李四','ls@qq.com');
这种情况下,虽然编号相同,但是姓名不同,是可以插入成功的
核心特性总结:
- 每张表只能有一个主键(但可以是复合主键)
- 主键列不允许为NULL且值必须唯一
- 自增主键(AUTO_INCREMENT)由数据库自动维护,插入时可省略或设为NULL:
- 自增主键的值基于当前最大值+1生成,即使删除中间记录也不会回退:
复合主键解析:
复合主键由多个字段组合而成,仅当所有字段值都相同时才判定为重复。典型应用于多对多关系表:
-- 允许插入(组合值不同)
INSERT INTO student_course VALUES (1, 101, 90);
INSERT INTO student_course VALUES (1, 102, 85);
INSERT INTO student_course VALUES (2, 101, 92);-- 禁止插入(组合值重复)
INSERT INTO student_course VALUES (1, 101, 88); -- 报错
主键设计原则:
- 优先使用与业务无关的自增ID作为主键(代理主键),避免业务字段变更导致的主键修改
- 主键字段应选择较小的数据类型(如INT、BIGINT),减少索引存储开销
- 复合主键应控制字段数量(建议不超过2个),避免降低查询效率
- 避免使用UUID作为主键(会导致索引碎片化,降低性能)
5. FOREIGN KEY:外键约束
定义:维护两个表之间的参照关系,确保从表(子表)的列值必须在主表的参照列中存在。保证一个表中的数据匹配另一个表中的值的参照完整性
应用场景:存在关联关系的表结构,如订单表关联用户表、学生表关联班级表、商品表关联分类表等。
解释: 表中某个列的值,必须是另一张表中的主键列,或是唯一约束列的值,也就是当前表中的值在另一张表中必须存在,且还要满足主键或是唯一约束
示例:
建立关联关系,那么关联关系的主外键的数据类型要与目标表中的数据类型要保持一致
-- 1. 先创建主表(被参照表)
CREATE table class(id BIGINT PRIMARY KEY auto_increment,name VARCHAR(20)
);-- 2. 创建从表(子表)并添加外键约束
CREATE TABLE student(id BIGINT PRIMARY KEY auto_increment,name VARCHAR(20) not null,class_id BIGINT
);
-- 建立关联关系,那么关联关系的主外键的数据类型要与目标表中的数据类型要保持一致
-- 为已有表添加外键约束
ALTER TABLE student
ADD CONSTRAINT fk_student_class
FOREIGN KEY (class_id) REFERENCES class(id);
但是此时的创建的两张表并没有设置了主外键的关系,所以这个时候我们可以会插入错误的数据
这个时候写入了一条学生记录,设置了不存在的班级编号,现在数据是可以写入成功的,所以需要设置`主外键
外键用于关联其他表的主键或者唯一键,语法:
foreign key (字段名) references 主表(列);
-- 1. 先创建主表(被参照表)
CREATE table class(id BIGINT PRIMARY KEY auto_increment,name VARCHAR(20)
);
-- 2. 创建从表(子表)并添加外键约束
CREATE TABLE student(id BIGINT PRIMARY KEY auto_increment,name VARCHAR(20) not null,class_id BIGINTforegin key (class_id) references class(id) -- 这个时候指定并设置外键
);
insert into student (name,class_id) values ('张三',1),('李四',1),('王五',2),('赵六',3)
这个时候写入正确数据的时候是可以插入成功的
这个时候写入一个主表的不存在的id记录
通过外键约束,保证数据的完整性和关系的正确性
在目前来说,我们用代码层面去进行校验,而不是让数据库自己去进行校验
是因为数据库自己校验的过程越多,性能便消耗的越严重,对于数据的保存的功能会有所影响,所以用代码进行剥离开,从而减少数据库的校验
删除主表中的数据
当子表中存在对主表的依赖的时候,那么能不能删除主表中相应的记录?
我们首先进行直接删除主表的的记录:
DELETE FROM class WHERE id=4; -- 报错:被student表引用
如果删除主表中的记录,就要子表中不能有对该条记录的依赖,也就意味要先删除子表中的记录,再去删除主表中记录
DELETE FROM student WHERE class_id=4;
DELETE FROM class WHERE id=4;
参照规则:
- 外键列的数据类型必须与主表参照列完全一致(如主表是INT,从表也必须是INT)
- 从表插入或更新记录时,外键值必须是主表中存在的值或NULL(允许无关联)
- 主表删除或更新被参照的记录时,受外键约束影响,默认不允许操作
外键动作处理:
当主表记录被删除或更新时,可以通过ON DELETE
和ON UPDATE
指定处理策略:
CREATE TABLE student (id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(50) NOT NULL,class_id INT,FOREIGN KEY (class_id) REFERENCES class(id)ON DELETE CASCADE -- 主表记录删除时,从表关联记录也删除ON UPDATE SET NULL -- 主表记录更新时,从表关联字段设为NULL
);
常用策略包括:
CASCADE
:级联操作(主表变动,从表相应变动)SET NULL
:主表变动时,从表外键设为NULLRESTRICT
:禁止操作(默认行为)NO ACTION
:不做处理(多数数据库等同于RESTRICT)
外键使用争议:
尽管外键能保证数据参照完整性,但在高并发系统中常被禁用,原因包括:
- 外键约束会增加数据库的性能开销(每次操作都需检查关联关系)
- 可能导致死锁(多表关联操作时)
- 分库分表场景下难以维护外键关系
替代方案是在应用程序层面实现参照完整性校验,通过业务逻辑保证数据关联正确。
6. CHECK:检查约束
定义:限制列值必须满足指定的条件(如范围、格式等)。
应用场景:需要对列值进行范围或格式限制的场景,如年龄必须为正数、成绩在0-100之间、性别只能是"男"或"女"等。
创建方式:
-- 建表时添加检查约束
CREATE TABLE test (id INT ,gender VARCHAR(1) ,CHECK (gender IN ('男', '女')), -- 性别只能是男女
);
数据库兼容性:
- MySQL 5.7及之前版本:会解析CHECK约束但不生效(忽略检查)
- MySQL 8.0及以上版本:完全支持CHECK约束
- PostgreSQL、Oracle、SQL Server:完全支持CHECK约束
使用建议:
- 由于MySQL的版本兼容性问题,重要的检查逻辑建议同时在应用层实现
- 检查条件应保持简单,复杂逻辑更适合在业务代码中处理
- 对于格式验证(如邮箱格式、手机号格式),建议使用正则表达式配合CHECK约束(部分数据库支持)
三、约束的管理与维护
1. 查看表中的约束
通过数据库系统表可以查询表中已定义的约束:
-- MySQL中查看约束
SELECT CONSTRAINT_NAME, COLUMN_NAME, CONSTRAINT_TYPE
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE TABLE_NAME = 'student';
2. 删除约束
-- 删除主键约束
ALTER TABLE student DROP PRIMARY KEY;-- 删除唯一约束
ALTER TABLE student DROP INDEX emp_no; -- 唯一约束以索引形式存在-- 删除外键约束
ALTER TABLE student DROP FOREIGN KEY fk_student_class;-- 删除检查约束
ALTER TABLE student DROP CONSTRAINT chk_age;-- 删除默认值约束(通过修改列定义)
ALTER TABLE student ALTER COLUMN status DROP DEFAULT;
3. 约束的性能影响
约束虽然保障了数据完整性,但也会带来一定的性能开销:
- 插入/更新性能:每次写入数据都需要验证约束,会增加操作耗时(尤其是外键和检查约束)
- 存储开销:唯一约束和主键约束会创建索引,占用额外存储空间
- 删除限制:外键约束可能导致删除操作需要级联检查,降低删除效率
性能优化建议:
- 只在必要的字段上添加约束,避免过度约束
- 高频写入的表应尽量减少外键约束,改用应用层校验
- 批量插入数据时,可以临时禁用非关键约束,完成后再启用
-- 临时禁用外键约束
SET FOREIGN_KEY_CHECKS = 0;-- 执行批量插入操作
INSERT INTO student (...) VALUES (...), (...), ...;-- 恢复外键约束
SET FOREIGN_KEY_CHECKS = 1;
四、约束设计的最佳实践
1. 主键设计规范
- 每张表必须有主键,优先使用自增INT/BIGINT类型
- 主键应与业务无关(避免使用身份证号、手机号等)
- 主键值一旦设置,不应修改(主键是记录的唯一标识)
- 复合主键仅用于多对多关系表,且字段数量不超过2个
2. 外键使用准则
- 中小型应用可以使用外键保证数据完整性
- 高并发、大数据量系统建议在应用层实现参照校验
- 外键命名应遵循
fk_从表名_主表名
规则,便于识别 - 合理设置
ON DELETE
和ON UPDATE
策略,避免意外数据丢失
3. 约束组合策略
- 主键 + 自增:最常用的唯一标识方案
- 非空 + 唯一:适用于登录账号等关键业务字段
- 非空 + 默认值:适用于有明确默认值的必填项
- 检查 + 非空:适用于有范围限制的必填字段
4. 版本兼容性处理
- MySQL 5.7中用枚举(ENUM)替代CHECK约束:
CREATE TABLE student (gender ENUM('男', '女') NOT NULL -- 等效于CHECK约束 );
五、总结
数据库约束是保障数据质量的核心机制,合理使用约束可以显著减少数据错误,降低系统维护成本,但过度使用也会带来性能损耗;在实际开发中,应根据系统规模和业务特点灵活选择约束策略;随着数据库技术的发展,新型数据库对约束的支持更加灵活,结合了关系型数据库的完整性保障和NoSQL的高扩展性。但无论技术如何变化和迭代,"数据完整性"始终是数据库设计的核心原则,而约束正是实现这一原则的基础工具。
本文的内容就先到这里了,如果有哪些地方的不足,欢迎大家在评论区中进行指正与批评,谢谢大家!