我们创建表 tt21,id 字段设置 primary key auto_increment(给 id 字段同时添加主键和自增长键),name 添加非空约束。通过desc tt21查看表结构,id 字段 Extra 列标注auto_increment;使用show create table tt21查看建表语句,字段定义末尾携带 AUTO_INCREMENT 关键字,从表结构层面确认自增配置生效。
下面我们插入数据:
连续三条插入语句只填写 name 字段、不给 id 传值,分别插入 'a'、'b'、'c'。查询数据表能够看到 id 自动依次生成 1、2、3,在用户缺省主键赋值时,自增机制从默认起始值开始逐次 + 1。
继续:
我们接着主动插入一条数据 (1000,'e'),手动把 id 赋值为 1000,这条数据正常入库。此时再次尝试插入 id=1000 会触发主键重复报错;只填 name 插入新数据时,新 id 自动变成 1001,由此可以得出关键规则:自增计数器会跟随表里现存最大 id 更新,手动写入更大数值后,后续自增从这个最大值继续 + 1。
最后我们通过 show create table tt21\G 查看完整建表语句,表的末尾标注了 AUTO_INCREMENT=1002,这个数值就是数据表下一次缺省插入时将要自动生成的 id,MySQL 会自动记录并维护自增的下一个可用值,让我们在建表阶段也能够预先手动指定自增起始数字。
索引的本质是用额外的磁盘存储空间换取查询执行时间(以空间换时间),想要提升查询速度,就需要单独开辟空间存放索引目录,对应书本目录会额外占用书页空间。结合前面学过的主键知识,主键在建表时 MySQL 会自动为主键字段生成主键索引,这也是 PRI 主键标识本质就是索引标记、自增字段必须依托索引才能生效的底层缘由,各类约束里主键和索引的绑定关系最为紧密。
首先一个事物会体现出各种各样的属性,而我们建表就是用来表述这些属性数据,所以 MySQL 中的表就相当于 C 语言中的结构体,用来描述对象的属性。而主键为了保证事物的唯一性,我们通常选择事物众多属性中就有唯一性的字段作为主键来标识唯一性,而往往真实场景中一个事物的众多属性中有相当一部分虽然没有被选择成为主键,但这些属性依旧需要唯一性来保证。作为主键,仅仅是因为这一列被选择成为了主键,仅此而已。所以我们不能认为我们已经选择了主键就不需要唯一键了,而正是因为我们选择了主键,而其他属性也是需要维护唯一性的,因此就有了唯一键,所以主键和唯一键不冲突,反而是相互补充的。
紧接着我们执行 delete from class where id=1;,直接在班级主表里删掉 id=1 的通信 101 班,删除语句正常执行。删除完成后班级表只剩 id=2 的数据,但学生表里张三、李四、王五三名学生的 class_id 全是 1,对应的班级记录已经被删掉。后续查询班级关联学生时,就会出现找不到所属班级的孤立数据;如果不靠上层程序代码做校验,数据库本身无法拦截这类不合理的删除,此时就得靠业务层手动判断,就会大幅增加开发工作量。
目前我们会发现 student 和 class 这两张表只是在业务逻辑上有关联,但是数据表层面没有外键约束绑定,也就是说两张表只有关联之名、没有约束之实:
一是新增学生时,能随意写入主表不存在的班级 id;
二是主表可以随便删除被从表数据引用的主键记录,造成从表数据悬空孤立。
那我们想要从数据库底层自动规避这两类错误,就需要给从表 student 的 class_id 字段添加外键,绑定主表 class 的主键 id,由 MySQL 自动校验主从数据合法性。
引入外键:
下面我们就引入外键约束来描述两张表之间的关系:
我们先执行 drop table student; 删掉原先无外键的学生表,重新创建 student 从表。在字段定义末尾写入 foreign key(class_id) references class(id) ,以此把从表的 class_id 字段和主表 class 的主键 id 绑定外键关系。执行desc student查看表结构,class_id 字段的 Key 列显示 MUL,MUL 标识就代表该字段绑定了外键索引,外键配置正式生效;
主键约束保证学生 id 全局唯一,唯一键约束保证手机号不能重复,新增的外键约束限制 class_id只能填写主表已经存在的班级编号,三层约束从不同维度规范数据,杜绝违规数据的写入。
在已经配置外键的前提下,当我们再次尝试插入 class_id=3 的学生,而主表 class 里不存在 id=3 的班级,数据库直接抛出外键约束报错。这体现外键第一条规则:从表外键字段的值,必须在主表关联主键中已存在,否则无法插入。
下来我们直接执行 delete from class where id=1 想要删掉 1 号班级,此时 student 从表里还有学生的 class_id=1 在引用这条班级数据,外键约束就会再次拦截删除操作并报错。这体现了外键的第二条规则:主表某条主键数据被从表外键引用时,不能直接删除主表这条记录,避免删完班级后,从表学生变成无归属的孤立数据。
只有我们清空了关联的学生后,主表班级表中的班级才能正常删除,继续:
我们先执行 delete from student where id=100;,删掉唯一归属 1 班的学生,查询 student 表后,已经没有任何学生的 class_id=1。再次执行delete from class where id=1;,删除 1 号班级语句执行成功,查询 class 表仅剩 id=2 的通信 102 班。所以外键默认的限制规则:想要删除主表的某条数据,必须先把从表里所有引用该主键的外键数据全部删除完毕,主表记录才可以被删除;只要从表还有引用,主表就禁止删除,依靠这个机制保证主从表数据完整性。
外键的本质就是把业务数据的关联校验交给 MySQL 数据库自动管控,提前在建表阶段告知 MySQL 两张表的约束规则。后续用户执行新增、删除 SQL 时,MySQL 自动校验数据是否符合关联逻辑,违背业务规则的操作直接报错拦截,从数据库底层杜绝不合规脏数据的生成,不用再依靠上层代码手动做业务判断。
十二、总结
所以约束表示的是一种关系吗?
约束不能简单的说成 "是一种关系",约束本身分成字段自身约束和表与表之间的关联约束两大类:
单字段约束 (非空、默认值、主键、唯一、zerofill、自增) 只作用在单个字段本身,管控当前字段的数据写入规则,只限制本列数据格式,不存在和别的字段以及别的表之间的关系。 比如 name not null 非空约束,只要求 name 不能存 NULL;primary key 主键只约束本表字段值唯一、非空,全程只管控单张表内部字段,没有跨表关联。