一、范式概述
1.1 什么是范式
范式(Normal Form,NF)是关系数据库设计中的一组规范规则。遵从不同级别的规范,可以设计出冗余度更低、异常更少的关系型数据库表结构。
范式的核心目标是:减少数据冗余、消除插入 / 删除 / 更新异常。
1.2 六种范式
关系数据库共有六种范式,级别依次升高:
| 范式 | 全称 | 核心要求 |
|---|---|---|
| 1NF | 第一范式 | 列不可再分(原子性) |
| 2NF | 第二范式 | 消除非主属性对候选键的部分函数依赖 |
| 3NF | 第三范式 | 消除非主属性对候选键的传递函数依赖 |
| BCNF | 巴斯 - 科德范式 | 消除主属性对候选键的部分 / 传递依赖(每个决定因素都是候选键) |
| 4NF | 第四范式 | 消除非平凡多值依赖 |
| 5NF | 第五范式(完美范式) | 消除连接依赖 |
范式越高,数据冗余越小,但表拆分越多,查询时可能需要更多 JOIN。
1.3 范式与性能的权衡
- 范式高:冗余少、一致性好、写入异常少,但查询可能需要多表 JOIN,增加查询复杂度和 IO;
- 范式低(反范式):冗余多、查询快(单表即可),但写入时需要维护多处数据的一致性,存在更新异常风险。
实际应用中,通常满足 3NF 即可;在高并发、读多写少的互联网场景,有时会故意反范式(增加冗余字段)来减少 JOIN、提升查询性能。
二、核心概念铺垫
在讲解范式之前,必须先理解几个关键概念。
2.1 函数依赖
函数依赖是指:在一张表中,如果属性(字段)X 的值确定后,属性 Y 的值也唯一确定,就称 Y 函数依赖于 X ,记作 X → Y。
例如:学生表中,学号确定后,姓名就唯一确定 → 学号 → 姓名。
三种函数依赖
| 类型 | 定义 | 示例 |
|---|---|---|
| 完全函数依赖 | X→Y,且 X 的任何真子集都不能决定 Y | (学号,课程号) → 成绩(缺了哪个都不行) |
| 部分函数依赖 | X→Y,但 X 的某个真子集就能决定 Y | (学号,课程号) → 姓名(其实学号 alone 就能决定姓名) |
| 传递函数依赖 | X→Y,Y→Z,且 Y 不能反过来决定 X,则 X→Z 是传递依赖 | 学号→班级号,班级号→班主任姓名 → 学号→班主任姓名(传递) |
2.2 候选键、主键、主属性、非主属性
| 概念 | 定义 |
|---|---|
| 候选键(Candidate Key) | 能唯一标识 表中一行记录的最小属性集合。可以是单个字段,也可以是多个字段的组合。一张表可以有多个候选键。 |
| 主键(Primary Key) | 从候选键中选一个作为行的唯一标识,一张表只能有一个主键。 |
| 主属性(Prime Attribute) | 出现在任何一个候选键中的属性。 |
| 非主属性(Non-prime Attribute) | 不出现在任何候选键中的属性。 |
示例
学生表(学号,身份证号,姓名,年龄):
- 候选键有两个:
{学号}和{身份证号}(都能唯一标识一个学生); - 选
{学号}作为主键; - 主属性:学号、身份证号(出现在候选键中);
- 非主属性:姓名、年龄。
三、第一范式(1NF)
3.1 定义
数据库表中的每一列都是不可再分的原子数据项,不能是集合、数组、对象等非原子数据。
换句话说:每一行的每一列,只存一个值,且这个值不能再拆分成更小的有意义的数据。
3.2 反例与正例
反例(不满足 1NF)
| 学号 | 姓名 | 联系方式 |
|---|---|---|
| 001 | 张三 | 138xxxx, 025-xxxx |
"联系方式" 一列存了手机号和座机号两个值,可以再拆分 → 不满足 1NF。
正例(满足 1NF)
| 学号 | 姓名 | 手机号 | 座机号 |
|---|---|---|---|
| 001 | 张三 | 138xxxx | 025-xxxx |
拆成两列,每列只存一个原子值 → 满足 1NF。
3.3 关于 SET 类型
MySQL 的 SET 类型可以在一列中存储多个值(如 '阅读,运动,音乐'),这天然违反 1NF。
但这不代表绝对不能用 SET:如果业务场景中这个字段就是作为一个整体使用、从不单独查询其中某个值,且能在冗余和效率之间做出权衡,SET 仍可使用。
四、第二范式(2NF)
4.1 定义
在满足 1NF 的前提下,消除非主属性对候选键的部分函数依赖。
换句话说:表中每一个非主属性,都必须完全函数依赖于整个候选键,而不能只依赖候选键的一部分。
如果主键是单一字段(非复合主键),则天然满足 2NF------ 因为单一字段不存在 "部分依赖" 的问题。
4.2 部分函数依赖的判定
假设复合主键是 (A, B):
- 如果某个非主属性 C,只靠 A 就能确定(不需要 B),那 C 就部分依赖于主键 → 不满足 2NF;
- 如果 C 必须同时靠 A 和 B 才能确定,那 C 完全依赖于主键 → 满足 2NF。
4.3 反例分析
样例 :将所有字段填入一个表,用 (学生id, 班级名称) 作为复合主键。
| 学生 id | 班级名称 | 学生姓名 | 学生性别 | 班级教室 | 班主任 |
|---|
分析各字段的依赖关系:
学生姓名、学生性别:只靠学生id就能确定 → 部分依赖(不需要班级名称);班级教室、班主任:只靠班级名称就能确定 → 部分依赖(不需要学生 id);- 没有任何非主属性是完全依赖于整个复合主键的。
→ 不满足 2NF。
4.4 不满足 2NF 导致的异常
| 异常类型 | 定义 | 说明 | 示例 |
|---|---|---|---|
| 插入异常 | 本该可以正常录入的数据,由于缺少主键的组成部分,违反主键非空约束,导致无法插入 | 想插入一个还没有学生的班级,无法插入(因为学生 id 是主键的一部分,不能为空) | 新建了一个班级但还没招生,无法录入班级信息 |
| 删除异常 | 删除某一条数据时,连带删除了本不该删除的其他实体信息,造成非预期的数据丢失 | 删除某班所有学生时,班级信息也一起被删掉了 | 某届学生全部毕业后删除学生记录,班级信息也没了 |
| 更新异常 | 修改某一个属性值时,需要同时修改多行数据;只要有一行漏改,就会产生数据不一致 | 修改班级信息时,需要修改所有该班学生的行,漏改一处就不一致 | 班级换教室,需要更新该班几十个学生的行 |
| 数据冗余 | 同一个班级的信息在每个学生行中重复存储 | 50 个学生的班级,班级名称 / 教室 / 班主任重复存 50 遍 |
核心本质 :不满足第二范式引发的插入、删除、更新异常,根源是将本应独立的多个实体强行揉合进同一张关系表,破坏了 "一表一主题" 的设计原则,使不同实体的数据与操作产生了不必要的强制绑定。往往是m:n关系的实体
范式逻辑对应 : 不满足 2NF 的核心特征,是非主属性对复合候选键存在部分函数依赖:非主属性的实际决定因素只是候选键中的一部分主属性,和候选键里的另一部分主属性并无依赖关系;但在存储层面,该非主属性必须与完整的候选键绑定,共同构成一行数据。
五、第三范式(3NF)
5.1 定义
在满足 2NF 的前提下,消除非主属性对候选键的传递函数依赖。
换句话说:非主属性必须直接依赖于候选键,不能通过其他非主属性间接依赖(传递依赖)。
5.2 传递函数依赖的判定
如果存在 候选键 → A → B,且 A 不能反过来决定候选键(表明A不是候选键),则 B 传递依赖于候选键 → 不满足 3NF。(往往是将两个1:n关系的实体揉合在一张表中)
5.3 反例分析
样例 :如果只用 学生id 作为单一主键:
| 学生 id | 学生姓名 | 所属班级 | 班级名称 | 班级教室 | 班主任 |
|---|
- 主键是单一字段
学生id→ 天然满足 2NF; - 但依赖关系:
学生id → 所属班级,所属班级 → 班级名称/教室/班主任; - 所以
班级名称/教室/班主任通过所属班级传递依赖 于学生id→ 不满足 3NF。
5.4 满足 3NF 的设计
拆成两张表:
班级表(主表)
| 班级 id(主键) | 班级名称 | 班级教室 | 班主任 |
|---|
学生表(从表)
| 学生 id(主键) | 学生姓名 | 学生性别 | 所属班级 id(外键) |
|---|
- 班级表中,所有字段都直接依赖于
班级id; - 学生表中,所有字段都直接依赖于
学生id(所属班级 id 是外键,直接依赖学生 id); - 消除了传递依赖 → 满足 3NF。
满足 3NF 后,插入异常、删除异常、更新异常和数据冗余都得到了显著改善。
六、BCNF(巴斯 - 科德范式)
6.1 定义
BCNF 是 3NF 的增强版。在满足 3NF 的前提下,对于任何非平凡的函数依赖 X→Y,X 必须是候选键。
换句话说:每个决定因素(能决定其他属性的属性)都必须是候选键,不允许非候选键的属性去决定其他属性。
6.2 3NF 与 BCNF 的区别
- 3NF 允许主属性对候选键的部分依赖或传递依赖;
- BCNF 连主属性的部分 / 传递依赖也消除了。
简单记:3NF 是 "非主属性不传递依赖于候选键",BCNF 是 "所有属性(包括主属性)都不传递 / 部分依赖于候选键"。
6.3 示例
1. 关系模式与业务规则
关系表:STJ(学生编号 S, 教师编号 T, 课程编号 J) 业务规则:
- 每位教师只讲授一门固定课程;
- 一门课程可以由多位不同的教师讲授;
- 每个学生选修某一门课程时,对应一位固定的授课教师;
- 学生可以选修多门课程。
2. 函数依赖
根据业务规则推导:
T → J:教师编号可以唯一确定课程(一个老师只教一门课)(S, J) → T:学生 + 课程的组合,可以唯一确定授课教师(S, T) → J:学生 + 教师的组合,可以唯一确定对应课程
3. 候选键与主属性
- 候选键 1:
(S, J)------ 能唯一标识一行记录,且去掉任意一个字段都失去标识能力 - 候选键 2:
(S, T)------ 能唯一标识一行记录,且去掉任意一个字段都失去标识能力 - 主属性:
S、T、J(三个属性都出现在候选键中,全都是主属性) - 非主属性:无
4. 范式判定
- 满足 3NF:3NF 的约束是「消除非主属性对候选键的部分 / 传递依赖」。本例中没有非主属性,天然满足 3NF 的全部要求。
- 违反 BCNF :BCNF 的核心规则是「关系中所有决定因素都必须是候选键」。 本例中存在函数依赖
T → J,决定因素是T,但 T 单独不是候选键(T 只能确定课程 J,无法确定学生 S,不能唯一标识整行记录)。 本质是存在「主属性 J 对 候选键 (S,T) 的部分函数依赖」,因此违反 BCNF。
5. 违反 BCNF 带来的异常
- 插入异常:新增一位教师并确定其授课课程时,只要还没有学生选修这门课,就无法插入数据(S 是主属性,不能为空)。
- 删除异常:删除某一名学生的选课记录,会连带删除掉「该教师教授这门课」的关联信息。
- 更新异常:某教师更换授课课程,所有选该教师课程的学生记录都需要同步修改,漏改就会数据不一致。
6. 修正为 BCNF 的方式
将原表拆分为两张表,消除不合理的依赖:
教师课程表(T, J),主键:T学生选课表(S, T),主键:(S, T) 拆分后两张表的所有决定因素都是候选键,均满足 BCNF。
七、反范式设计
7.1 什么时候需要反范式
范式越高,表拆得越细,查询时 JOIN 越多。在以下场景,有时会故意违反范式、增加冗余:
| 场景 | 反范式做法 | 目的 |
|---|---|---|
| 读多写少、查询频繁 | 在订单表中冗余存储用户名、商品名 | 查询订单时不用 JOIN 用户表、商品表 |
| 统计报表 | 预先计算好统计值存入汇总表 | 避免每次查询都做复杂聚合 |
| 历史数据不可变 | 订单快照保存下单时的商品价格 | 商品价格变动不影响历史订单 |
7.2 反范式的代价
- 数据一致性维护成本高:修改冗余字段时,需要同时更新所有存储该字段的表;
- 写入性能下降:一次写入可能涉及多张表的更新;
- 存在不一致风险:如果更新时漏了某处,就会出现数据不一致。
反范式是用空间和一致性维护成本换查询性能,需要根据业务场景权衡,不能盲目反范式。
八、E-R 图
8.1 什么是 E-R 图
E-R 图(Entity-Relationship Diagram,实体 - 关系图)是一种用于描述数据模型的概念图,主要用于数据库设计阶段,帮助理清实体、属性和实体之间的关系。
8.2 基本组成
| 元素 | 表示 | 含义 | 类比 |
|---|---|---|---|
| 实体(Entity) | 矩形框 | 数据对象,如学生、班级、课程 | 类似表 |
| 属性(Attribute) | 椭圆形 / 圆角矩形 | 实体的特性,如学生的姓名、年龄 | 类似字段 |
| 关系(Relationship) | 菱形框 | 实体之间的联系,并标注关系类型 | 类似外键 / 中间表 |
8.3 关系类型
1. 一对一(1:1)
一个实体 A 的实例对应另一个实体 B 的一个实例,反之亦然。
示例:用户 和 账户
- 用户属性:昵称、头像、手机号、地址......
- 账户属性:登录用户名、密码
设计方式:可以在任一实体中保存关联另一实体的字段(外键)。通常把不常用的字段拆分到另一张表,减少主表的宽度。

2. 一对多(1:N)/ 多对一(N:1)
一个实体 A 的实例对应多个实体 B 的实例,但一个实体 B 的实例只对应一个实体 A 的实例。
示例:班级 和 学生(一个班级有多个学生,一个学生只属于一个班级)。
设计方式 :在多的一方 (学生表)保存外键,关联一的一方(班级表)的主键。
3. 多对多(M:N)
一个实体 A 的实例对应多个实体 B 的实例,反之亦然。
示例:学生 和 课程(一个学生选多门课,一门课被多个学生选)。
设计方式 :必须引入中间表(也叫连接表 / 关联表),将多对多拆成两个一对多。
8.4 多对多的中间表设计
以学生选课为例,引入成绩表作为中间表:
- 一个学生有多个成绩 → 学生:成绩 = 1:N
- 一个课程有多个成绩 → 课程:成绩 = 1:N
成绩表字段:
| 字段 | 说明 |
|---|---|
| id | 主键,自增 |
| student_id | 外键,关联学生表 |
| course_id | 外键,关联课程表 |
| score | 成绩 |
中间表的主键可以是自增 id,也可以用
(student_id, course_id)作为复合主键(确保一个学生一门课只有一个成绩)。
8.5 EE-R图: 从 SQL 反推 E-R 图
也可以通过已有的建表 SQL 反向生成 E-R 图(逆向工程)。
注意:
- 工具是通过外键约束来识别表之间的关系的;
- 如果建表时没有建立外键(只是逻辑上有关联),工具不会自动连线;
- 因此逆向生成的 E-R 图不一定完全符合设计意图,需要人工核对。
工具推荐:
- MySQL Workbench(免费,官方工具,支持逆向工程);
- Navicat(付费,也有 E-R 图功能);
- draw.io / diagrams.net(免费,手动绘制)。
