【MySQL】数据库约束 及 表的设计

数据库约束是关系型数据库的一个重要功能,数据库约束是指对数据表中的数据所施加的规则或条件,主要作用是保证数据的完整性,也可以理解为用于确保数据的准确性和可靠性 (数据本身是否正确,关联关系是否正确);人工检查数据完整性的工作量非常大,在数据表中定义一些约束,那么数据库写入数据的时候,数据库就会帮我们做校验 工作。约束一般是指定在列上的。

约束可以是基于数据类型、值范围、唯⼀性、非空等规则,以确保数据的正确性和相容性。

约束类型

|------------------|------------------------------------------------------|
| 类型 | 说明 |
| NOT NULL 非空约束 | 指定非空约束的列不能存储 NULL 值 |
| DEFALUT 默认约束 | 当没有给列赋值时使用的默认值 |
| UNIQUE 唯⼀约束 | 指定唯⼀约束的列每行数据必须有唯⼀的值 |
| PRIMARY KEY 主键约束 | NOT NULL 和 UNIQUE的结合,可以指定⼀个列或多个列,有助于防止数据重复和提高数据的查询性能 |
| FOREIGN KEY 外键约束 | 外键约束是⼀种关系约束,用于定义两个表之间的关联关系,可以确保数据的完整性和⼀致性 |
| CHECK 约束 | ⽤于限制列或数据在数据库表中的值,确保数据的准确性和可靠性 |

1.NOT NULL 非空约束

定义表时某列不允许为NULL时,可以为列添加非空约束。

示例:在某个数据库中的一个表中,没有指定非空约束时,当前的列是允许写入一个NULL值的

如果想要将数据表中的某一列定义为一个必填项,那么就可以使用 not null 非空约束:想要将student表中的 id 列设置为非空约束,那么我们在创建student表的过程中,就需要在相应的列加入约束,设置 id 列不能为空,即添加非空约束:

此时向表中添加数据时,就会对 id 列进行校验,保证当前列不能为空:

非空列有值时可以正常写入:

2.UNIQUE 唯一约束

指定了唯⼀约束的列,该列的值在所有记录中不能重复,比如⼀个⼈的⾝份证号,学⽣的学号等。

示例:不加唯一约束的时候,可能出现编号相同,但是人名不同的情况,不符合逻辑:

那么我们就可以在创建表时,在指定列中添加一个唯一约束:查看表结构,Key列显示 UNI 表示唯⼀约束:

插入重复学号的学生时,唯一约束生效:

3.DEFALUT 默认值约束

DEFAULT 约束用于向列中插入默认值,如果没有为列设置值,那么会将默认值设置到该列

示例:没有默认值约束时,如下图所示,姓名列如果没有赋值,那么默认的值就是NULL:

当我们想要设置某一列的默认值时,就可以使用默认值约束,为当前列指定默认值:

此时添加一行数据,没有为 name 列赋值,也就是插入时只指定了 id 列,那么name列的默认值就是 '佚名':

虽然指定了默认值,但是当我们手动指定这一列的值为NULL时,插入的值依然是NULL,而并不是'佚名'了,因为这个NULL是我们自己手动指定的,也可以理解为我们想要的值 - 用户指定的值优先级高于默认值约束。也就是说,当为某一列设置了默认值约束时,如果不给这个列指定值才会使用默认值约束

4.PRIMARY KEY 主键约束

NOT NULL 和 UNIQUE 的结合,主键约束确保某列(或多个列的结合)有唯一的标识,主键约束既是非空的也是唯一的。

  • 主键约束唯⼀标识数据库表中的每条记录。
  • 主键必须包含唯⼀的值,且不能包含 NULL 值。
  • 每个表只能有⼀个主键,可以由单个列或多个列组成。
  • 通常为每张表都指定⼀个主键,主键列建议使用BIGINT类型。

示例:重构学生表,为 id 列添加非空和唯一约束

查看表结构,添加了非空和唯⼀约束之后Key列显示PRI标识主键:

如以下的示例,id 列重复时,唯一约束生效,id 列为NULL时,非空约束生效,即主键约束生效:

我们想要设置主键列时,可以直接使用它的关键子primary key 即可,而不使用 not null unique。

注意:主键约束校验了非空和唯一,这两个校验在写入数据时对效率是有一定影响,但是比起不做校验来说,这个性能消耗还是可以承担的,强烈建议为每张表定义一个主键

通常把主键列设置为自动增长,让数据库维护主键值,不需要自己手动计算了:具体如何维护:

在插入的时候先找到最大值,然后在这个最大值的基础上加1,生成一个新的值,作为新一个数据行的主键(id)值

通过上述的自增操作,在插入数据时,将主键值设置为NULL (写入NULL可以插入成功,这里并不是把NULL写入数据库,而是说让数据库帮我们处理这个列的值-自动增长) 或者 是不指定主键,主键列的值都可以自动生成,也就是说全列插入或者是指定列插入,都可以自动生成 id ,作为数据行的主键

如果某条记录写入失败,新生成的主键值将会作废,示例:此时插入一条新的学生数据,此时自增的 id 应该是3,但是由于学号'1002'重复,发生唯一冲突,导致插入失败,那么 id 为3的主键值作废;修改完学号后插入成功,查询表后发现这条新数据行的 id 列/主键列的值为4。

由此我们可以知道,主键值可以不连续,也就是说,我们可以自己指定一个主键值,只要不重复即可:

前面说过,在插入新数据时,会先找到主键值中的最大值,然后在这个基础上重新加1,此时数据表中最大值是100,那么就在100的基础上加1作为新记录的主键值:

注意:一个表中不能有多个主键

但是一个主键可以包含多个列,也就是复合主键,复合主键:由多个列共同组成的主键,主键是否冲突以多个列的组成进行判定。

语法:

sql 复制代码
primary key(列[,列...]);

如上述的表结构,Key列表示当前表定义了复合主键在唯一校验时,只有复合主键中所有的列都相同才会被判定为相同

5.FOREIGN KEY 外键约束

保证一个表中的数据匹配另一个表中的值的参照完整性。

  • 外键用于定义主表和从表之间的关系
  • 外键约束主定义在从表的列上,主表关联的列必须是主键或唯⼀约束
  • 当定义外键后,要求从表中的外键列数据必须在主表的主键或唯⼀列存在或为null

图例解说:

**表中某一个列的值,必须是另一张表中的主键列,或是唯一约束列的值,也就是当前表中的某一列值在另一张表中必须存在,且满足主键或者唯一约束。**示例:一个学校有A,B两个班,有一个同学想要进入学校,他告诉我是C班的,那我就可以快速判断此同学不是学校的学生,这是一个校验的过程。

创建一个班级表和学生表,学生表中有一个列是班级编号,写入数据时这个编号必须是有效的:

sql 复制代码
create table class(
    id bigint primary key auto_increment,
    name varchar(20)
);

create table stu(
    id bigint primary key auto_increment,
    name varchar(20) not null,
    class_id bigint
);

此时创建表的时候没有设置主外键关系,那么写入一条学生记录,设置了不存在的班级编号,数据可以写入成功,这是不符合逻辑的:

因此,我们需要使用外键约束,

外键用于关联其他表的主键或唯一键,语法

sql 复制代码
foreign key (从表列名) references 主表(列名);

重构学生表(从表),加入外键约束:

看表结构,Key列中 MUL 表示外键约束的列,表示存在主外键关系:

当写入一条主表中不存在的 id 记录时,写入失败:

思考:当从表中存在对主表的依赖,也就是说存在主外键关系时,能不能删除主表中对应的记录呢?

显然是不可以的,依然会报主外键关系的错误:

如果想要删除主表中的记录,从表中就不能有对该条记录的依赖,也就是说要先删除从表中的依赖记录,再去删除主表中要删除的记录。

6.CHECK 约束

保证列中的值符合指定的条件,也就是检验数据的有效性,对于MySQL数据库,对check子句进行分析,但是忽略check子句。

示例:为 gender 列设置了 CHECK约束 ,当为该列赋的值不是check允许的值,那么会报错:

在8.0.16开始全面支持CHECK约束,之前的版本会忽略CHECK的定义,也就是不生效。一般对数据的有效性校验是在代码层面做的。

表的设计

我们在设计表的时候,会遵循一些规则,一般称这些规则为 三大范式

1.范式

数据库的范式是⼀组规则。在设计关系数据库时,遵从不同的规范要求,设计出合理的关系型数 据库,这些不同的规范要求被称为不同的范式。

关系型数据库有六种范式:第⼀范式(1NF)、第⼆范式(2NF)、第三范式(3NF) 、巴斯-科德 范式(BCNF)、第四范式(4NF)和第五范式(5NF,⼜称完美范式),越高的范式数据库冗余越小。这里我们主要了解一二三范式

第一范式 1NF

  1. 在关系型数据库的设计中,满足第⼀范式是对关系模式的基本要求。不满足第⼀范式的数据库就不 能被称为关系数据库。
  2. 第一范式要求:数据库表中的每一个列都是不可进行拆分的原子数据项,而不能是集合,数组,对象等非原子数据。

示例:定义一个学生表,需要记录学生信息和学校信息。

  • 正例:学校信息包含在⼀行中,每⼀列都不能再进行拆分,此时已满足第⼀范式

虽然这样不符合数据库设计的规范,但是每一列都是不可再分的,最起码可以表明一个学生和班级、学校之间的关系。

  • 反例:学校是⼀个对象,可以继续进行拆分,所以不满足第⼀范式

说明:在定义表的时候,对照到数据中的数据类型,每个字段都可以用一个数据类型表示,那么当前这个表就天然满足第一范式。例如在正例中,id、学生、学校名等都可以用一个数据类型表示,而在反例中,学校列是一个对象,,并没有一个数据类型可以用来表示学校。

第二范式 2NF

在满足第⼀范式的基础上,不存在非关键字段对任意候选键的部分函数依赖(存在于表中定义了复合主键的情况下)

  • 非关键字段:非主键列/字段
  • 候选键:可以理解为主键、外键、没有主键时的唯一键

示例:需求 - 学生可以选修课程,课程有对应的学分,学生考试后每门课程会产生相应的成绩

  • 反例:用一张表记录所有信息
  1. 表中所有列都是不可拆分的,满足第一范式。
  2. 这张表中使用学号+课程名定义复合主键来唯⼀标识⼀个学⽣某门课程的成绩,这也是这张表的主要作用。
  3. 学生是通过学号来确定的,学生的姓名、年龄和性别与课程没有关系,即学生的信息只依赖学号, 不依赖课程名;学分是通过课程来确定的,课程的学分与学生没有关系,即学分只依赖课程名,不 依赖学生。
  4. 对于使用复合主键 的表,如果⼀行数据中的有些列只与复合主键中的⼀个或其中几个列有关系,那么就说他存在部分函数依赖,也就不满足第⼆范式。

不满足第二范式可能出现的问题:

  1. 数据冗余:学生的姓名、年龄、性别和课程的学分在每行记录中重复出现,造成了大量的数据冗余。
  2. 更新异常:如果要调整MySQL的学分,那么就需要更新表中所有关于MySQL的记录,⼀旦执行中断导致某些记录更新成功,某些数据更新失败,就会造成表中同⼀门课程出现不同学分的情况,出现数据不一致问题。
  3. 插入异常:目前这样的设计,成绩与每⼀门课和学生都有对应关系,也就是说只有学生参加选修课程考试取得了成绩才能生成⼀条记录。当有⼀门新课还没有学生参加考试取得成绩之前,那么这门新课在数据库中是不存在的,因为成绩为空时记录没有意义。
  4. 删除异常:把毕业学生的考试数据全都删除,此时课程和学分的信息也会被删除掉,有可能导致⼀段时间内,数据库里没有某门课程和学分的信息。
  • 正例:针对需求应该设计三张表,即:学生表、课程表和成绩表
    • 对于这种有两个或多个关键字段(复合主键)决定一条记录且存在部份依赖的情况,可以设计多张表,每张表都有非主键字段,且都强依赖/完全依赖于整个主键而不是部份依赖,满足了第二范式
    • 以下学生表中,只有一个主键 id,该表中其他的列完全依赖于 id 主键,符合第二范式;课程表中,有一个主键 id ,该表中的其他列完全依赖于这个主键,符合第二范式;而成绩表中,有学生id和课程id两个主键,该表中的其他列,也就是成绩列完全依赖于这个复合主键,而不是部份依赖,因此也符合第二范式。

说明:第⼆范式强调的是部分函数依赖,要消除部分函数依赖(非主键字段仅依赖于主键的一部分),当⼀张表中的主键只有⼀列(没有复合主键)时,天然满足第⼆范式;而如果是一张表中的主键有多个列(复合主键)时,要确保任意非主键字段对主键完全依赖,才满足第二范式

第三范式 3NF

在满足第⼆范式的基础上,不存在非关键字段,对任⼀候选键的传递依赖。

示例:要求学生表中记录学生所属的学院,在满足第⼆范式的基础上对学生表做出修改。

  • 反例
  1. 该表满足第一范式,所有非主键属性(学号、姓名、学院、学院电话)都完全依赖于主键id(单一主键),所以也满足第二范式。
  2. 因为是要描述学生信息,并且在表中定义了Id为主键,Id可以明确的标识每条学生信息
  3. 在这个表结构中,可以看出学生的学号、姓名、年龄、性别与主键Id强相关;学院电话、学院地址与学院强相关;在⼀个表中出现了两个强相关的关系,而且这两个强相关关系又存在传递现象 ,即 通过学生Id可以找到学生记录,学生记录中包含学院名,每个学院又有自己的电话和地址,即通过id可以找到学院的电话, Id → 学院 → 学院电话,学院电话依赖于学院 而非直接依赖于 Id
  4. 这种传递现象称为传递依赖(非主键列不直接依赖于主键,而是间接依赖),所以当前的表不满足第三范式
  • 正例:把学院信息拆分出来定义学院表,学⽣表与学院表做关联
    • 这样设计,两张表都依赖与自己表中的主键id,学生表可以通过外键与学院表之间建立关联关系

说明:第三范式强调的是传递依赖,要消除传递函数依赖(非主键字段通过另一个非主键字段间接依赖于主键),确保非主键字段只直接依赖于主键,而不存在间接依赖,才能满足第三范式

第三范式可以解决数据冗余、更新异常、插入异常、删除异常的问题。

2.设计过程

设计一个表的方法:

  1. 从现实业务中抽象得到概念类,概念类是从现实世界中抽象出来的,在需求分析阶段就需要确定下来,类对应到数据库中就是实体,实体在数据库中表现为一张一张的表,类中的属性就对应着表中的字段(列)。
  2. 确定类与类之间的关系,即实体与实体间的关系,并画出E-R画(实体-关系图)
  3. 最后根据E-R图完成SQL语句的编写并创建数据库

实体-关系图

实体-关系图(Entity-Relationship Diagram)简称E-R图,也称作实体联系模型、实体关系模型,是 ⼀种用于描述数据模型的概念图,主要用于数据库设计阶段

E-R图的基本组成
  • 实体:即数据对象,用矩形框表示,比如用户、学生、班级等。
  • 属性:实体的特性,用椭圆形或圆角矩形表示,如学生的姓名、年龄等。
  • 关系:实体之间的联系,用菱形框表示,并标明关系的类型,并用直线将相关实体与关系连接起来。
关系类型
a. 一对一关系(1:1)

以一个登录界面为例:

这样的场景,一般对应着两个实体,一个实体是用户 (比如学生和老师),还有一个实体是账号

  • ⼀个用户实体包含的属性有:用户昵称,真实姓名,手机号,邮箱地址,性别,学校;
  • ⼀个账户实体包含的属性有:登录用户名,密码。
  • 用户实体与账户实体是⼀对⼀的关系,⽤E-R图表示如下:

一个用户只能有一个账号,一个账号只能给一个用户使用,不能共享,这就是一对一的关系。

针对一对一关系,设计表时,有两种方式:

  • 1.把两个实体所有的信息全部放在一张表中
sql 复制代码
user(user_id,name,age,phone_num,mail,username,password);
     ---------------------------------------------------------------------------------------------  ------------------------------------------------
            关于用户的信息                关于账号的信息
  • 2.创建两张表,分别记录用户信息和账号信息,并把两张表关联起来

关联方法1:

sql 复制代码
user(id,name,age,phone_num,mail);
account(id,username,password,user_id); //在账户表中通过外键将其与用户表关联起来

关联方法2:

sql 复制代码
user(id,name,age,phone_num,mail,account_id); //在用户表中通过外键将其与账户表关联起来
account(id,username,password); 

显然,上述的登录界面是通过关联方法1的方式登录的,即通过账户去找到用户信息:通过用户名和账号密码这两个字段确定了一条账号记录,然后可以找到 user_id,最后通过 user_id 可以在用户表中找到用户记录。

b. 一对多关系(1:N)

以学生和班级之间的关系为例:

  • ⼀个学⽣实体包含的属性有:真实姓名,学号,年龄,性别,入学时间
  • ⼀个班级实体包含的属性有:班级名,学生⼈数
  • 一个学生只能存在于一个班级中,而一个班级可以有多个学生,所以班级实体与学生实体是一对多的关系,反过来说学生实体与班级实体是多对一的关系,用E-R图表示如下:

针对一对多关系,设计表:

  • 分别为不同的实体创建表,然后建立表与表之间的关联关系

关联方法1:该方法是在班级表中通过外键与学生表关联,但是一个班级中的学生不止一个,不可以通过班级记录中的student_ids来表示班级中有哪些学生,而且在关系型数据库中没有集合的类型,所以不能使用 关联方法1 这样的方式去创建表

sql 复制代码
student(id,name,age);
class(id,name,student_ids);

关联方法2:该方法是在学生表中通过外键与班级表关联,通过学生记录中的class_id表示学生在哪个班级,是正确的做法。

sql 复制代码
class(id,name);
student(id,name,age,class_id);

即下表中的做法:每个学生都关联了一个班级

c. 多对多关系(M:N)

以学生和选修课程的关系为例:

  • ⼀个学⽣实体包含的属性有:真实姓名,学号,年龄,性别,⼊学时间
  • ⼀个课程实体包含的属性有:课程名
  • ⼀个学⽣可以选修改多门课程,⼀门课程也可以被多名学⽣选修改,所以学⽣与课程之间是多对多 关系,⽤E-R图表示如下:
  • 对于多对多关系,可以使用中间表进行记录,比如⼀个学⽣参加了某⼀门课程的考试得到了相应的 成绩,用E-R图表示如下:

针对多对多关系,设计表:

分别创建课程实体表和学生实体表,然后创建一个关系表,在关系表中为实体之间建立关联关系

  • 通过关系表可以清晰的把学生选修的课程记录下来,这样设计同时也满足了第二范式的要求,如果需要修改学生年龄只需要修改学生表中的年龄字段/列,不会影响到关联表。
sql 复制代码
course(id,name);
student(id,name,age);
student_course(id,student_id,course_id);

练习-设计表

根据以上E-R图完成表的创建,并添加主键列

a. 用户与账号的一对一关系

实体间⼀对⼀关系只需要在其中⼀个实体中添加对另⼀个实体的关联字段即可

示例:在账户实体中添加对用户实体的关联:

b.学生与班级的一对多关系

分别创建学生表和班级表,在学生表中添加⼀列与班级表建立关联关系

c.学⽣、课程与成绩的多对多关系

学⽣可以选修多门课程,每门课程考试后会产⽣⼀个成绩,两个表之间没有办法直接建立关系,所以要用到⼀个记录成绩的中间表

相关推荐
码云数智-大飞2 小时前
Oracle RAS:AI时代守护企业数据安全的智能盾牌
数据库·人工智能·oracle
bubuly2 小时前
软件开发全流程注意事项:从需求到运维的全方位指南
大数据·运维·数据库
我真的是大笨蛋3 小时前
Redo Log详解
java·数据库·sql·mysql·性能优化
fengxin_rou3 小时前
Redis 从零到精通:第一篇 初识redis
数据库·redis·缓存
爱学习的阿磊3 小时前
Python上下文管理器(with语句)的原理与实践
jvm·数据库·python
m0_736919103 小时前
Python面向对象编程(OOP)终极指南
jvm·数据库·python
OceanBase数据库官方博客3 小时前
滔搏基于OceanBase实现 15TB到0.9TB“无痛切换”与“系统瘦身”
数据库·oceanbase·分布式数据库
Jess073 小时前
MySQL内置函数
数据库·mysql
OceanBase数据库官方博客3 小时前
爱奇艺基于OceanBase实现百亿级卡券业务的“单库双擎”架构升级
数据库·架构·oceanbase·分布式数据库