MySQL表设计全解析:三大范式与表关系设计

文章目录

  • 一、数据库表设计流程
    • [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.3 第三范式(3NF)](#4.3 第三范式(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))
  • 八、三种关系对比总结

一、数据库表设计流程

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)

满足第二范式必须:

  1. 先满足第一范式(1NF)
  2. 所有非主属性 必须完全依赖主键
  3. 不能存在部分函数依赖

⚠️ 重点:

  • 只有在复合主键情况下才可能违反第二范式
  • 如果主键只有一列(非复合主键),天然满足第二范式

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)解决的是:

非主属性对主键的 传递依赖问题

要达到第三范式,必须:

  1. 满足第一范式(字段不可再分)
  2. 满足第二范式(无部分依赖)
  3. 不存在传递依赖

4.3.1什么是传递依赖?

如果存在:

主键 → A

A → B

那么就会形成:

主键 → B(通过 A 传递)

这就叫 传递依赖


4.3.2第三范式(3NF)正反例说明

反例(不满足第三范式):

错误设计:学生表

学号 姓名 年龄 所在学院 学院地址 学院电话

说明:

  • 主键:学号
  • 学号 → 姓名、年龄(正常)
  • 学号 → 所在学院(正常)
  • 所在学院 → 学院地址、学院电话(问题在这里),这里形成了依赖链,学号 → 所在学院 → 学院地址、学院电话,也就是说:学院地址、学院电话不是直接依赖学号,而是依赖"所在学院",这就是传递依赖,不满足第三范式。
    一个表中混合了两个实体的信息:学生,学院,这会导致:学院信息重复存储,修改学院电话要修改很多行,删除最后一个学生会把学院信息删掉。

正例(满足第三范式):

正确设计:学生表

  • 第一步:拆分实体
    学院表
学院编号(主键) 学院名 学院电话 学院地址

学生表

学号(主键) 姓名 年龄 学院编号(外键)

主键:学号

外键:学院编号

现在依赖关系变成:

学生表:

学号 → 姓名、年龄、学院编号

学院表:

学院编号 → 学院名、学院电话、学院地址

不存在:

主键 → A → B

因此满足 3NF


4.3.2.1判断是否违反 3NF:

  1. 表中是否包含两个实体?
  2. 是否存在:主键 → A → B?

只要有"中间传递",就是不满足第三范式。


五、一对一关系设计

场景:登录系统

  • 登录时使用的是 账号(用户名 + 密码)
  • 登录成功后展示的是 用户信息(姓名,班级等)

从业务上分析,可以抽象出两个实体:

  1. 用户(User)
  2. 账号(Account)

5.1 实体分析

1️⃣ 用户(User)

记录个人信息:

  • 姓名
  • 年龄
  • 手机号
  • QQ
  • 邮箱
  • 班级等

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 建立中间表

相关推荐
倔强的石头_2 小时前
国产化时序替换落地指南:用金仓数据库管好海量时序数据
数据库
java干货2 小时前
Slave 的 SQL 线程为什么追不上 Master?
数据库·sql
紫金桥软件3 小时前
【紫金桥跨平台实时数据库】的技术架构与工程实践
数据库·架构·自动化·跨平台
逍遥德3 小时前
如何学编程之理论篇.03.如何做数据库表结构设计?
开发语言·数据库·性能优化·代码规范·代码复审
Hello eveybody3 小时前
如何将十进制转为二进制、八进制、十六进制?
前端·javascript·数据库
a285283 小时前
最新SQL Server 2022保姆级安装教程【附安装包】
数据库·性能优化
小刘的大模型笔记3 小时前
向量数据库深度剖析:核心优劣势 + 适用场景,避开 RAG 落地的选型坑
数据库·人工智能·深度学习·机器学习
马猴烧酒.3 小时前
【面试八股|Mysql篇】Mysql常见面试题详解笔记
笔记·mysql·面试
w***29853 小时前
开放自己本机的mysql允许别人连接
数据库·mysql·adb