上一篇文章中,我们学习了 MySQL 各类字段数据类型,我们清楚:数据类型本身就是最基础的字段约束,依靠类型限制数据格式、取值范围,非法数据会直接被数据库拦截。但仅靠数据类型做约束,管控规则比较单薄,很多业务层面的数据规范没法实现,比如邮箱不能重复、用户账号不能为空、主键自动编号这类需求,因此 MySQL 额外提供了表级别的约束,作为数据校验的补充规则。
MySQL 日常开发里高频使用的约束一共分为多类,这里我们重点讲解:null/not null(空约束)、default(默认值约束)、comment(字段注释)、zerofill(零填充)、primary key(主键约束)、unique key(唯一约束)、auto_increment(自增约束)等常用规则,逐个拆解语法、实操案例与适用场景。
下面我们先来看第一个约束 --- 非空约束:
二、not null 非空约束
在正式讲解非空约束之前,我们首先要厘清 MySQL 里 NULL 和空字符串 '' 的区别,这也是理解非空约束的前提。和 C 语言中 NULL 代表数字 0 的定义不同,MySQL 中的 NULL 代表该字段没有任何数据、是空值,而空字符串''是字段已经存入内容,只不过内容为空字符,二者在存储和运算规则上完全不一样。
MySQL 字段默认的空属性是 NULL,也就是建表不额外声明约束时,字段默认允许存入空值。但在实际项目开发中,我们会尽量把核心字段设置为 NOT NULL 非空约束,核心原因在于 NULL 无法参与常规算术运算,就像示例里执行 select 1+NULL;,最终运算结果依旧返回 NULL,空值参与计算会直接导致统计、求和等业务 SQL 出错,为了规避这类运算异常,业务字段优先限制非空。 非空约束 not null 的作用是在建表时绑定在字段上,被添加该约束的字段,插入数据时不能写入 NULL 空值,如果插入语句不给这个字段赋值、或是主动填入 NULL,数据库会直接拦截这条数据、抛出报错,从数据库层面强制保证该字段一定存在有效数据。
举例:
下面我们结合一个常见的场景来理解非空约束,理解 NOT NULL的设计意义。比如在一张班级的信息表中,班级名称、教室是两条核心必填信息,缺少班级名称就无法区分班级,没有教室信息就无法确定上课地点,因此这两个字段不允许为空,此时我们就可以通过 NOT NULL 约束在数据库层面强制限制。
下面我们建表,创建 myclass 表,class_name 和 class_room 两个字段后添加 not null 约束,剩余 other 字段不添加约束,使用数据库的默认规则。建表结束后执行 desc myclass 查看表结构,能看到添加非空约束的字段在 Null 列标识为NO,代表禁止存入 NULL 空值,无约束的 other 字段 Null 列为YES,保持默认可空状态,此时就能直观的区分是否为空约束的差异。
我们再通过 show create table myclass\G 查看建表原生语句,没有指定约束的 other 字段自带DEFAULT NULL,含义是插入数据时若不给该字段传值,数据库自动填充 NULL 空值,这也是 MySQL 字段的默认配置。后续插入数据时,若不给 class_name 或 class_room 赋值、或是主动传入 NULL,数据库会直接抛出报错拦截数据,以此实现非空约束的校验效果,从底层规避关键字段空值带来的数据异常问题。
那怎么体现约束呢?
下面我们插入多组语句,class_name、class_room 带有 NOT NULL 非空约束,other 字段默认可为空。 首先执行第一条完整插入语句,class_name、class_room、other 三个字段全部传入有效值 '高三2班'、'101教室'、'普通班',所有字段数据都符合约束规范,这条数据顺利插入数据表,查询后可以完整看到三条字段的内容。
紧接着第二条插入语句,我们只给两个非空字段赋值 '高三3班'、'103教室' ,省略 other 字段的传参。因为 other 没有非空约束、默认配置 DEFAULT NULL,MySQL 在缺省赋值时会自动填充 NULL,语句正常执行入库,查表后能看到这条记录的 other 列显示为 NULL,这也印证了可空字段的默认填充规则。
之后我们做两条违规插入,用来体现非空约束的拦截作用。第一种场景,插入时只填写class_name,完全忽略 class_room 字段,数据库报错提示字段没有默认值,无法完成插入;第二种场景,主动给 class_room 传入 NULL 空值,直接触发cannot be null报错。两类报错本质一致:class_room 被 NOT NULL 约束限定,既不能缺省不传,也不能手动写入 NULL。
此时我们就能得出非空约束的核心作用:被 NOT NULL 修饰的字段,插入数据时必须提供有效内容,缺省或写入 NULL 都会被数据库拦截,从物理层面保证关键字段永远存在有效值,避免空值带来后续运算、统计异常。
我们继续通过 alter 语句去掉 a、b 字段的 zerofill 属性,再使用 show create table t17\G 查看建表语句,就会发现不带 unsigned 的普通 int 默认是int(11) ,添加 unsigned 无符号修饰的 int 默认是int(10)。这是为什么呢?
因为从存储空间原理来讲,int 固定占用 4 字节,有符号 int 取值上限是 2 的 31 次方,换算十进制最大为 2147483647,总共 10 位有效数字,因此无符号 unsigned 默认显示宽度 10;而有符号 int 还要额外占用 1 位表示符号位,取值下限是负数,最长数字连同负号合计 11 位,所以普通 int 默认显示宽度 11,这就是 MySQL 在建表时自动填充宽度数值的底层原因。
此时我们就可以总结一下 : int (M) 括号里的 M 是显示宽度,和 int 本身能存储的数值范围无关;只是代表显示宽度,单独写 int 类型时这个宽度不会生效,只有搭配 zerofill 才会体现作用 :数据位数<M,左边自动补 0 凑齐 M 位;数据位数 ≥ M,直接原样输出原值;补零仅作用于查询结果,物理存储始终是原始数字。
六、primary key 主键约束
接下来我们学习 primary key 主键约束:
主键是数据表里用来唯一标识单条记录的核心约束,它具备两大硬性规则:
字段数据不允许重复、不允许存入 NULL 空值
同时一张数据表最多只能设置一个主键字段
在实际业务设计中,我们需要依靠主键区分每一行数据,因此绝大多数业务表都会设计主键,主键字段也普遍选用整型编号,比如学号、用户 ID 这类唯一性的数据。
举例:
我们继续创建表 test_key,将 id int unsigned 字段后添加 primary key 设为主键,name 字段添加非空约束。执行 desc test_key 查看表结构可以发现,主键字段的 Null 列自动变成 NO,Key 列标注 PRI,这个标识就代表当前字段是主键。由此能看出 : 主键自带非空约束,添加数据时默认不能添加空数据,因此在建表时也不用额外添加 not null。
下面我们插入数据:
我们首先插入第一条数据 (1,'张飞'),数据正常插入。再次尝试插入 (1,'刘备'),因为主键 id=1 已经存在,出现主键重复报错,无法写入;更换 id 为 2 之后,(2,'刘备') 顺利插入。这组 SQL 直观体现主键唯一性:主键值全表唯一,重复数值会被数据库直接拦截,保证每条记录依靠 id 就能区分。
我们先执行 alter table test_key drop primary key; 删除表中原有的主键约束,执行完通过 desc test_key 查看表结构,id 字段的 Key 列原本的 PRI 标识消失,但之前主键自带的 NOT NULL 属性会保留。主键移除之后唯一性约束随之失效,此时再插入一条 (2,'孙权'),即便数据表已经存在 id=2 的数据,语句也能正常入库,表内出现两条 id 同为 2 的记录,直观说明主键消失后,重复数据不再被拦截。
那如果我们要再添加主键时呢?
在表中已经存在重复 id=2 的数据前提下,执行 alter table test_key add primary key(id); 试图重新给 id 追加主键,数据库直接报主键重复错误。这是主键的底层规则:给已有数据的字段添加主键时,数据库会全表校验数据,一旦字段内出现重复值,无法建立主键索引,添加操作直接失败。从这个现象可以总结开发规范:尽量在建表初期就定义好主键,避免大批量插入数据后再回头补主键,重复数据会阻碍主键创建。
上面我们是建表时添加的主键,那现在我们在建表后添加主键:
想要顺利重建主键,必须先清理冲突数据,这里执行 delete from test_key where name='孙权'; 删掉 id 重复的这条记录,表中 id 字段全部变成唯一值。再次执行添加主键语句 alter table test_key add primary key(id);,语句执行成功,desc 查看表结构,id 的 Key 列重新变回 PRI。此时再尝试插入(2,'孙权'),主键唯一性约束重新生效,重复 id 直接报错拦截。
最后使用 show create table test_key\G 查看完整建表源码,在建表语句末尾可以看到PRIMARY KEY (id),从建表定义层面确认 id 字段已经成功恢复为主键,完整闭环验证了主键删除、事后添加的整套约束逻辑。
第一组插入 (1234,40,90);第二组插入 (1235,40,85),学生 id 不同、课程 id 相同,组合不重复,可以正常插入;第三组 (1234,41,90),学生 id 相同、课程 id 不一样,组合依旧唯一,同样插入成功。从三条数据能够看出:复合主键允许其中某一列单独重复,只要两列不同时重复就符合约束。
最后尝试再次插入 (1234,40,90),学生 id 和课程 id 和已有记录完全重合,字段组合出现重复,数据库抛出主键重复报错。由此总结复合主键核心:把参与主键的所有字段当成一个整体,字段组合唯一是硬性要求,单个字段各自可以重复,但是字段不能同时重复,这也是复合主键和单列主键最核心的区别。