文章目录
- 一、数据库表设计流程
-
- [1.1 从需求中获取类](#1.1 从需求中获取类)
-
- [1.1.1 对应关系](#1.1.1 对应关系)
- [1.2 确定类与类之间的关系](#1.2 确定类与类之间的关系)
- [1.3 使用 SQL 创建具体的表](#1.3 使用 SQL 创建具体的表)
- [二、表设计规范 ------ 三大范式](#二、表设计规范 —— 三大范式)
-
- [2.1 什么是范式?](#2.1 什么是范式?)
- 三、数据库关系类型
- 四、范式分类
-
- [4.1 第一范式(1NF)](#4.1 第一范式(1NF))
- [4.2 第二范式(2NF)](#4.2 第二范式(2NF))
-
- 4.2.1第二范式(2NF)正反例说明
- 4.2.2不满足第二范式(2NF)可能出现的问题
-
- [4.2.2.1 数据冗余](#4.2.2.1 数据冗余)
- [4.2.2.2 更新异常(Update Anomaly)](#4.2.2.2 更新异常(Update Anomaly))
- [4.2.2.3插入异常(Insert Anomaly)](#4.2.2.3插入异常(Insert Anomaly))
- [4.2.2.4 删除异常(Delete Anomaly)](#4.2.2.4 删除异常(Delete Anomaly))
- 4.2.2.5核心问题总结
- [4.3 第三范式(3NF)](#4.3 第三范式(3NF))
-
- 4.3.1什么是传递依赖?
- 4.3.2第三范式(3NF)正反例说明
- [4.3.2.1判断是否违反 3NF:](#4.3.2.1判断是否违反 3NF:)
- 五、一对一关系设计
-
- [5.1 实体分析](#5.1 实体分析)
- [5.2 两个实体之间的关系](#5.2 两个实体之间的关系)
- 5.3一对一关系如何设计表?
- [5.3.1 方式一:合并为一张表](#5.3.1 方式一:合并为一张表)
- [5.3.2 方式二:拆成两张表(推荐 ⭐)](#5.3.2 方式二:拆成两张表(推荐 ⭐))
- [六、一对多关系设计(1 : N)](#六、一对多关系设计(1 : N))
-
- [6.1 什么是一对多?](#6.1 什么是一对多?)
- [6.2 一对多如何设计表?](#6.2 一对多如何设计表?)
-
- [6.2.1 错误设计 ❌](#6.2.1 错误设计 ❌)
- [6.2.2 正确设计 ✅](#6.2.2 正确设计 ✅)
- [七、多对多关系设计(M : N)](#七、多对多关系设计(M : N))
-
- [7.1 什么是多对多?](#7.1 什么是多对多?)
- [7.2 多对多不能直接建外键](#7.2 多对多不能直接建外键)
- [7.3 正确做法:建立"中间表"](#7.3 正确做法:建立“中间表”)
- 7.4设计实例
-
- [7.4.1 多对多的核心总结](#7.4.1 多对多的核心总结)
- 八、三种关系对比总结
一、数据库表设计流程
OOA(面向对象分析) → OOD(面向对象设计) → OOP(面向对象编程)
1.1 从需求中获取类
- 从业务需求中分析出 类
- 类对应数据库中的 实体
- 实体在数据库中表现为一张张 表
- 类中的 属性 对应表中的 字段(列)
1.1.1 对应关系
类 → 实体 → 表
属性 → 字段(列)
1.2 确定类与类之间的关系
在数据库中体现为表与表之间的关系:
- 一对一(1:1)
- 一对多(1:N)
- 多对多(M:N)
1.3 使用 SQL 创建具体的表
通过 SQL 语句(如 CREATE TABLE)实现表结构设计。
二、表设计规范 ------ 三大范式
设计表时需要遵守一定规则,这些规则称为:
数据库三大范式(Normal Form)
2.1 什么是范式?
范式是描述 数据关系模型规范程度 的标准。
三、数据库关系类型
- 一对一关系
- 一对多关系
- 多对多关系
四、范式分类
4.1 第一范式(1NF)
要求:
- 字段必须具有原子性(不可再分)
示例:
✅ 正确:
| id | name |
|---|---|
| 1 | 张三 |
❌ 错误:一个字段内包含了两门成绩
| 学号 | 姓名 | 课程成绩 |
|---|---|---|
| 001 | 张三 | 数学80,英语90 |
4.2 第二范式(2NF)
满足第二范式必须:
- 先满足第一范式(1NF)
- 所有非主属性 必须完全依赖主键
- 不能存在部分函数依赖
⚠️ 重点:
- 只有在复合主键情况下才可能违反第二范式
- 如果主键只有一列(非复合主键),天然满足第二范式
4.2.1第二范式(2NF)正反例说明
正例(满足第二范式):
1️⃣学生表
| 学号(主键) | 姓名 | 年龄 |
|---|
说明:
- 主键:学号
- 姓名、年龄完全依赖学号
- 不存在部分依赖
2️⃣ 课程表
| 课程编号(主键) | 课程名 | 学分 |
|---|
说明:
- 主键:课程编号
- 课程名、学分完全依赖课程编号
3️⃣ 学生选修成绩表
| 学号 | 课程编号 | 成绩 |
|---|
说明:
- 复合主键:(学号, 课程编号)
- 成绩依赖于"学生 + 课程"
- 必须通过两个字段才能确定成绩
✅ 正例总结
- 每张表都有主键
- 所有非主属性都完全依赖主键
- 不存在只依赖主键一部分的字段
反例(不满足第二范式):
1️⃣学生选修课成绩表(错误设计)
| 学号 | 学生姓名 | 年龄 | 课程名 | 学分 | 成绩 |
|---|
说明:
- 假设复合主键:(学号, 课程名)
- 学生的姓名,年龄和和课程名没有关系,即学生的姓名只依赖于学号,不依赖于课程
- 学分于学生没有关系,即学分只依赖于课程,不依赖于学号
- 对于两个或多个关键字共同决定一条记录的情况,如果一行中的有些字段只与关键字段中的一个有关系,这种情况就称为部分依赖,不满足第二范式。
4.2.2不满足第二范式(2NF)可能出现的问题
4.2.2.1 数据冗余
表现(针对上面不满足2NF设计的表)
- 学生姓名、年龄重复出现
- 课程学分重复出现
例如:
如果有 100 个学生选修 MYSQL
那么 MYSQL 的学分会重复存 100 次。
后果
- 占用大量存储空间
- 数据维护成本高
- 容易出现不一致
4.2.2.2 更新异常(Update Anomaly)
- 场景
MYSQL 课程学分从 50 调整为 60 - 问题
必须更新所有关于 MYSQL 的记录。
如果:有些记录更新成功,有些记录更新失败
就会出现: 同一门课程出现不同学分。
导致
数据不一致。
4.2.2.3插入异常(Insert Anomaly)
场景
学校新开一门课程:但还没有学生选修。
- 课程名:Python
- 学分:40
问题
由于成绩表是围绕"学生 + 课程"建立的:
- 没有学生,就无法插入课程信息
- 必须虚构一个学生成绩才能插入课程数据
导致:
课程信息无法独立存在
4.2.2.4 删除异常(Delete Anomaly)
场景
某门课程最后一个学生退选,删除该学生成绩记录时:
- 同时把课程学分信息删除了
导致:
课程信息丢失
4.2.2.5核心问题总结
第二范式解决的是"部分依赖问题",
如果存在部分依赖,就会产生数据冗余、更新异常、插入异常和删除异常。
4.3 第三范式(3NF)
第三范式(3NF)解决的是:
非主属性对主键的 传递依赖问题
要达到第三范式,必须:
- 满足第一范式(字段不可再分)
- 满足第二范式(无部分依赖)
- 不存在传递依赖
4.3.1什么是传递依赖?
如果存在:
主键 → A
A → B
那么就会形成:
主键 → B(通过 A 传递)
这就叫 传递依赖
4.3.2第三范式(3NF)正反例说明
反例(不满足第三范式):
错误设计:学生表
| 学号 | 姓名 | 年龄 | 所在学院 | 学院地址 | 学院电话 |
|---|
说明:
- 主键:学号
- 学号 → 姓名、年龄(正常)
- 学号 → 所在学院(正常)
- 所在学院 → 学院地址、学院电话(问题在这里),这里形成了依赖链,学号 → 所在学院 → 学院地址、学院电话,也就是说:学院地址、学院电话不是直接依赖学号,而是依赖"所在学院",这就是传递依赖,不满足第三范式。
一个表中混合了两个实体的信息:学生,学院,这会导致:学院信息重复存储,修改学院电话要修改很多行,删除最后一个学生会把学院信息删掉。
正例(满足第三范式):
正确设计:学生表
- 第一步:拆分实体
学院表
| 学院编号(主键) | 学院名 | 学院电话 | 学院地址 |
|---|
学生表
| 学号(主键) | 姓名 | 年龄 | 学院编号(外键) |
|---|
主键:学号
外键:学院编号
现在依赖关系变成:
学生表:
学号 → 姓名、年龄、学院编号
学院表:
学院编号 → 学院名、学院电话、学院地址
不存在:
主键 → A → B
因此满足 3NF
4.3.2.1判断是否违反 3NF:
- 表中是否包含两个实体?
- 是否存在:主键 → A → B?
只要有"中间传递",就是不满足第三范式。
五、一对一关系设计
场景:登录系统
- 登录时使用的是 账号(用户名 + 密码)
- 登录成功后展示的是 用户信息(姓名,班级等)
从业务上分析,可以抽象出两个实体:
- 用户(User)
- 账号(Account)
5.1 实体分析
1️⃣ 用户(User)
记录个人信息:
- 姓名
- 年龄
- 手机号
- 邮箱
- 班级等
2️⃣ 账号(Account)
记录登录信息:
- 用户名
- 密码
5.2 两个实体之间的关系
一个用户只能有一个账号
一个账号只能属于一个用户
这是典型的:
一对一关系(1 : 1)
5.3一对一关系如何设计表?
一对一关系在数据库中通常有两种实现方式。
5.3.1 方式一:合并为一张表
把用户信息和账号信息放在同一张表中。
sql
user(
user_id,
name,
age,
phone_num,
mail,
username,
password
);
特点:
-
所有信息集中在一张表
-
适用于用户和账号强绑定、不会分离的场景
-
查询方便
缺点:
-
表字段变多
-
账号信息与用户信息耦合严重
-
后期扩展不灵活
5.3.2 方式二:拆成两张表(推荐 ⭐)
分别建立:
用户表
sql
user(
user_id,
name,
age,
phone_num,
mail
);
账号表
sql
account(
account_id,
username,
password,
user_id
);
通过 user_id 建立关联。
六、一对多关系设计(1 : N)
6.1 什么是一对多?
举例:
学生 和 班级
- 一个班级可以有多个学生
- 一个学生只能属于一个班级
站在班级角度:1
站在学生角度:N
所以是:一对多关系(1 : N)
6.2 一对多如何设计表?
设计原则:
外键放在"多"的一方
6.2.1 错误设计 ❌
sql
class(
class_id,
class_name,
student_ids -- 不要这样设计,关系型数据库里面没有集合类型
);
-
1.关系型数据库没有"集合类型"
-
2.student_ids 会变成一个可分字段,违反第一范式(1NF)
6.2.2 正确设计 ✅
拆成两张表:
1️⃣ 班级表(1的一方)
sql
class(
class_id PRIMARY KEY,
class_name
);
2️⃣学生表(多的一方)
sql
student(
student_id PRIMARY KEY,
name,
age,
class_id -- 外键
);

可以通过class_id表示学生在哪个班级,上面student表中二班有2个学生,三班1个,四班1个学生。
七、多对多关系设计(M : N)
7.1 什么是多对多?
举例:学生 和 课程
- 一个学生可以选修多门课程
- 一门课程可以被多个学生选修
所以是:
学生 ⇄ 课程
多对多关系(M : N)
7.2 多对多不能直接建外键
错误理解 ❌:
- 在 student 表里加多个 course_id
- 在 course 表里加多个 student_id
原因:
- 关系型数据库没有"集合类型"
- 会违反第一范式(字段不可再分)
7.3 正确做法:建立"中间表"
设计步骤:
第一步:分别创建两个实体表
1️⃣ 学生表
sql
student(
student_id PRIMARY KEY,
name,
age
);
| student_id | name |
|---|---|
| 1 | 张三 |
| 2 | 李四 |
2️⃣ 课程表
sql
course(
course_id PRIMARY KEY,
course_name
);
| course_id | course_name |
|---|---|
| 1 | MYSQL |
| 2 | JAVA |
第二步:创建关系表(中间表)
sql
student_course(
id PRIMARY KEY,
student_id,
course_id
);
3️⃣ 选课关系表
| student_id | course_id |
|---|---|
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
表示:
- 张三选了 MYSQL 和 JAVA
- 李四选了 MYSQL
7.4设计实例
1️⃣班级表(班级编号,班级名)
sql
create table class(
class_id bigint primary key auto_increment,
name varchar(50) not null
);
2️⃣学生表(学生编号,学号,姓名,年龄,邮件,班级编号)
sql
create table student(
student_id bigint primary key auto_increment,
sn varchar(6) unique,
name varchar(50) not null,
age int,
mail varchar(50),
class_id bigint,
foreign key (class_id) references class(class_id)
);
3️⃣课程表(课程编号,课程名)
sql
create table course(
course_id bigint primary key auto_increment,
name varchar(50) not null
);
4️⃣成绩表(编号,学生编号,课程编号,成绩)
sql
create table score(
score_id bigint primary key auto_increment,
student_id bigint,
course_id bigint,
score decimal(5,2),
foreign key (student_id) references student(student_id),
foreign key (course_id) references course(course_id)
);
- 班级表与学生表之间是一对多的关系,一个班级对应多个学生
- 学生表与课程表之间是多对多的关系(一个学生可以选择多门课程,一门课程可以被多门学生选择),通过成绩表进行关联。
7.4.1 多对多的核心总结
设计口诀:
多对多
必须加中间表
八、三种关系对比总结
| 关系类型 | 设计方式 |
|---|---|
| 1 : 1 | 外键 + 唯一约束 |
| 1 : N | 外键放在多的一方 |
| M : N | 建立中间表 |