数据库的约束和设计

        本篇博客介绍了我对数据库约束和设计的基础认识,基本操作流程,他们在数据库中的呈现形式是怎样的。

一. 数据库的约束

1.1 什么是约束:

        数据库约束是用于确保数据完整性和一致性的规则,它们限制了可以插入、更新或删除的数据类型。当我们插入的数据不符合预计要求时会报出相应的错误。

1.2 常见的约束类型:

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

1.3 常见约束的实例:

1.3.1 NOT NULL非空约束

这是不添加任何约束条件的创建表展示的结构信息。

数据即使为NULL也可正常插入

添加约束条件之后我们可以发现在NULL那一行变成了NO这时我们再给名字那一列赋值NULL就会报如下错误:

而正常的值是可以正常插入的。

1.3.2 DEFAULT默认值插入

在没有设置default默认值时如果不插入年龄列,就会默认null。

这是加入默认值的效果:

1.3.3 UNIQUE 唯⼀约束

不设置唯一约束时id列允许重复

设置了唯一约束后如果出现插入的有重复的id就会报错。

1.3.4 PRIMARY KEY 主键约束

主键约束唯⼀标识数据库表中的每条记录。

主键必须包含唯⼀的值,且不能包含 NULL 值。

每个表只能有⼀个主键,可以由单个列或多个列组成。

通常为每张表都指定⼀个主键,主键列建议使⽤BIGINT类.

为id列进行非空约束和唯一约束,并查看表结构

可以发现Key对应的id多了PRI的字样,这就是主键的标志。

当id列重复时就会出现主键冲突。

我们通常会把主键设置为自动增长,用来避免重复数据,让数据库来维护主键值。

当id列设为自增主键后,插入的数据就会保持着id不重复的原则,id列不赋值也会在原先最大的id值下自增一。

当已有id值后再重复插入就会报错。

注意:当某条数据插入失败时,自增的值也会作废,下一次插入就会在作废的自增值基础上加一。

在一个表中,主键是唯一的,如果出现两个主键就会报错。

1.3.5 FOREIGN KEY 外键约束

外键⽤于定义主表和从表之间的关系.

外键约束主定义在从表的列上,主表关联的列必须是主键或唯⼀约束.

当定义外键后,要求从表中的外键列数据必须在主表的主键或唯⼀列存在或为null。

1.创建班级表(主表),并初始化班级表数据

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

insert into class (name) values ('java01'), 
('java02'), 
('java03'), 
('C++01'),
('C++02');

select* from class;

2.构建学生表,并添加外键约束。

create table student (id bigint PRIMARY KEY auto_increment,
name varchar(20) not null,
age int DEFAULT 18,
class_id bigint,
foreign key (class_id) references class(id) # 创建外键约束
)
desc student;

查看表结构会发现创建外键约束的那一列key值变为了MUL

正常插⼊数据

insert into student(name, class_id) 
values ('张三', 1), 
('李四', 2);
select * from student;

插⼊⼀个班级号为100的学⽣,由于主表中没有这个班级,插⼊失败

插⼊班级Id为NULL的记录,可以成功,表⽰当前学⽣还没有分配置班级

insert into student(name, class_id) values ('王五', NULL);
select* from student;


1.3.6 CHECK 约束

可以应⽤于⼀个或多个列,⽤于限制列中可接受的数据值,从⽽确保数据的完整性和准确性。
 

创建一个含有check约束的表:

create table student (id int,
name varchar(20),
age int,
gender char(1), 
check (gender ='男' or gender = '女'));

正常插入数据

insert into student(name, age, gender) values ('张三', 17, '男'), ('李
四', 19, '⼥');

当插入性别的不为男或女时

二.数据库的设计

2.1 什么是数据库范式

简单说,数据库范式是设计关系型数据库的 “洁癖规则” —— 遵守这些规则,能减少数据重复(冗余),避免更新、插入、删除时出乱子(异常)。

关系数据库有 6 种范式,从 1NF 到 5NF,等级越高,数据越 “干净”,但操作也可能越复杂(比如多表关联导致 IO 变多)。在实际工作中,满足第三范式(3NF)就足够应对 90% 以上的场景,没必要追求更高范式。

下面我们从最基础的 1NF 开始,用 “学生信息管理” 的例子,一步步拆解每级范式的要求和意义。

2.1.1第一范式(1NF)

核心要求:表的每一列必须是 “不可分割的原子数据”,不能是集合、对象、数组这类 “能再拆” 的内容。不满足 1NF 的数据库,连 “关系型数据库” 的门槛都没摸到。

反例:比如设计 “学生表” 时,把 “学校信息” 作为一列:

这里的 “学校” 列就像一个 “打包盒”—— 里面包含了学校名、地址、电话,还能继续拆分,显然不满足 1NF。这样设计会导致什么问题?想单独查 “所有学生的学校电话”,根本没法直接查,必须拆分字符串,效率极低。

正例:把 “学校” 拆成 “学校名”“学校地址”“学校电话” 三列,每一列都不能再拆:

2.1.2 第二范式(2NF)

核心要求:在满足 1NF 的基础上,非关键字段必须完全依赖主键(不能只依赖主键的一部分)。这个规则主要针对 “复合主键” 的场景(比如用 “学号 + 课程名” 做主键)。

反例:假设我们用一张表记录 “学生选修课程的成绩”,主键是 “学号 + 课程名”(复合主键):

这张表看似能记录所有信息,实则问题一大堆:

  • 数据冗余严重:张三的 “姓名”“年龄” 重复出现 2 次,MySQL 的 “学分” 重复出现 2 次,若有 100 个学生选 MySQL,学分就要重复 100 次;
  • 更新异常:要把 MySQL 的学分从 50 改成 55,得更新所有选 MySQL 的记录 —— 万一中间断了,有的记录是 50,有的是 55,数据就乱了;
  • 插入异常:新开设一门 “Python” 课,还没人选,就没法插入记录(因为 “学号” 为空,主键不完整);
  • 删除异常:删除毕业学生的成绩记录(比如 10001 的所有成绩),连 MySQL、Java 的课程信息也被删了。

问题根源是什么?非关键字段只依赖复合主键的一部分

  • “姓名”“年龄” 只依赖 “学号”(和 “课程名” 没关系);
  • “学分” 只依赖 “课程名”(和 “学号” 没关系)。这种 “部分依赖”,就是 2NF 要解决的核心问题。
正例:拆成 3 张表,消除部分依赖

既然 “学生信息”“课程信息”“成绩” 是三类不同的内容,那就拆成 3 张表,让每一张表只管一件事:

  1. 学生表:管学生的基础信息,主键 “学号”

学号姓名年龄性别
10001张三18
10002李四19
  1. 课程表:管课程的基础信息,主键 “课程 ID”

课程ID课程名学分
1MYSQL50
2JAVA60
  1. 成绩表:管学生和课程的关联成绩,主键 “学号 + 课程 ID”(复合主键)

学号课程ID成绩
10001198
10001290
10002189

这样一来,数据不再重复,更新、插入、删除也不会出乱子 —— 改 MySQL 学分,只改课程表 1 行;新增 Python 课,直接插课程表;删学生成绩,也不会影响课程信息。

2.1.3第三范式(3NF):

核心要求:在满足 2NF 的基础上,非关键字段不能依赖其他非关键字段(即不能有 “传递依赖”)。简单说,就是表中的每一列,都应该直接和主键 “挂钩”,而不是通过其他列 “间接挂钩”。

反例:有传递依赖的学生表

假设我们在 “学生表” 里加了 “学院” 和 “学院电话”:

这张表满足 2NF(非关键字段都依赖主键 “学号”),但有 “传递依赖”:学号 → 学院 → 学院电话“学院电话” 不直接依赖 “学号”,而是依赖 “学院”—— 这就是 3NF 要禁止的。

这种设计会导致什么问题?如果计算机学院换了电话,要更新所有计算机学院学生的 “学院电话” 列,还是会有冗余和更新异常。

正例:拆成 “学生表” 和 “学院表”,消除传递依赖

把 “学院相关信息” 拆成单独的 “学院表”,学生表只存 “学院 ID”(关联学院表):

  1. 学院表:管学院信息,主键 “学院 ID”

学院ID学院名学院电话
1计算机学院010-88888888
2信息安全学院010-66666666
  1. 学生表:通过 “学院 ID” 关联学院表

学号姓名年龄学院ID
10001张三181
10002李四192

现在,“学院电话” 直接依赖 “学院 ID”(主键),“学院 ID” 依赖 “学号”(主键),没有传递依赖 —— 改学院电话,只改学院表 1 行,完美!

2.2 实体之间的三种关系

2.2.1. 一对一关系(1:1):

场景:一个用户只能有一个账户,一个账户只对应一个用户(比如 APP 的 “用户信息” 和 “登录账户”)。

设计技巧:在其中一个表加 “关联字段” 即可。比如在 “用户表” 加 “account_id”(关联账户表的主键),或在 “账户表” 加 “user_id”(关联用户表的主键)。

2.2.2 一对多关系(1:N):

场景:一个班级有多个学生,一个学生只属于一个班级 —— 这是最常见的关系。

设计技巧:在 “多” 的一方加关联字段。比如在 “学生表” 加 “class_id”(关联班级表的主键)—— 因为一个班级有多个学生,学生表需要通过 “class_id” 找到自己所属的班级。

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

场景:一个学生可以选多门课程,一门课程可以被多个学生选 —— 这种关系不能直接用两个表实现,必须加 “中间表”。

设计技巧:加 “中间表”(比如 “成绩表”),中间表存两个实体的主键(学生 ID、课程 ID),还能存关系的额外属性(比如 “成绩”)。这样,“学生 - 成绩表 - 课程” 就把多对多拆成了两个一对多。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值