前言
范式(Normal Form)是数据库设计中的一套理论,用于消除数据冗余、减少数据异常。它通过一系列规则,将复杂的表结构分解为更简单、更规范的形式。
简单来说,范式就是**“拆表”的规矩**,级别越高,规矩越严,表拆得越细。
范式
建议:在大多数业务系统中,设计到 3NF 通常就足够了,既能保证数据规范,又不会因为过度拆分导致查询性能下降。
第一范式 (1NF):原子性
核心要求:表中的每个字段都必须是不可再分的最小数据单元(原子性)。
- 通俗理解:一个格子只能放一个值,不能放“一篮子”东西。
- 违反示例:一个“爱好”字段里存了“篮球,足球,音乐”三个值。
- 修正方法:要么拆成三个字段(爱好1、爱好2、爱好3),要么拆成多行(一个人对应多条爱好记录)。
第二范式 (2NF):消除部分依赖
前提:必须满足 1NF。
核心要求:表中所有非主键字段必须完全依赖于主键,不能只依赖主键的一部分。
- 通俗理解:一张表只描述一件事。如果主键由多个字段组成,非主键字段不能只跟其中一部分有关系。
- 违反示例:选课表(学号, 课程号, 成绩, 学生姓名)。
- 问题:学生姓名只依赖于“学号”,不依赖于“课程号”,这就是部分依赖。
- 修正方法:拆表。把“学生姓名”单独放到学生表里。
第三范式 (3NF):消除传递依赖
前提:必须满足 2NF。
核心要求:非主键字段之间不能有依赖关系(即不能存在 A->B->C 的传递链)。
- 通俗理解:非主键字段之间不能“互相决定”。
- 违反示例:学生表(学号, 姓名, 学院, 院长)。
- 问题:学号决定学院,学院决定院长,院长传递依赖于学号。
- 修正方法:拆表。把“学院”和“院长”单独放到学院表里。
BCNF (Boyce-Codd 范式):更严格的 3NF
核心要求:主键中的任何一个字段(或候选键)都不能被非主键字段决定。
- 通俗理解:在 3NF 的基础上,进一步消除了主键字段之间的“内部依赖”。
- 违反示例:仓库表(仓库ID, 存储物品, 管理员ID),假设一个仓库可以存多种物品,一个管理员只负责一个仓库。
- 问题:管理员ID 决定了 仓库ID(即非主键决定了主键的一部分)。
- 修正方法:拆表。把“管理员”和“仓库”的关系单独成表。
范式拆分示例
初始表格
学生选课记录(非1NF状态)
| 学号 | 姓名 | 班级 | 课程列表 | 教师 | 教师职称 | 课程号 | 成绩 |
|---|---|---|---|---|---|---|---|
| 1001 | 张三 | 软件1班 | 数据库(S001),数据结构(S002) | 王老师, 李老师 | 教授, 副教授 | C001, C002 | 85, 92 |
| 1002 | 李四 | 软件1班 | 操作系统(S003) | 赵老师 | 讲师 | C003 | 88 |
这张表存在以下违反范式的问题:
- 违反1NF:“课程列表”、“教师”、“教师职称”等字段包含多个值,不是原子数据。
- 主键模糊:无法用一个字段唯一标识一条记录。
满足1NF(确保原子性)
解决方式:将复合值的字段拆成多行,使每行、每列都对应一个单一值。
| 学号 | 姓名 | 班级 | 课程号 | 课程名 | 教师 | 教师职称 | 成绩 |
|---|---|---|---|---|---|---|---|
| 1001 | 张三 | 软件1班 | S001 | 数据库 | 王老师 | 教授 | 85 |
| 1001 | 张三 | 软件1班 | S002 | 数据结构 | 李老师 | 副教授 | 92 |
| 1002 | 李四 | 软件1班 | S003 | 操作系统 | 赵老师 | 讲师 | 88 |
此时变化:
- 一个学生选多门课,就对应多行数据。
- 可以设定联合主键为
(学号, 课程号),因为这两个字段可以唯一确定一行。 - 但还存在数据冗余和更新异常:
- 如果“张三”从“软件1班”转到“软件2班”,需要修改他所有选课记录。
- “教师职称”完全依赖于“教师”,而不是主键,这为后续范式要解决的问题。
满足2NF(消除部分依赖)
核心:在满足1NF的基础上,消除非主属性对主键的部分依赖。即,非主属性必须完全依赖于整个主键
解决方案:将表拆解,让每张表描述一个独立的事物。
拆表结果:
- 学生表(描述学生实体)
| 学号(主键) | 姓名 | 班级 |
|---|---|---|
| 1001 | 张三 | 软件1班 |
| 1002 | 李四 | 软件1班 |
- 课程表(描述课程实体)
| 课程号(主键) | 课程名 | 教师 |
|---|---|---|
| S001 | 数据库 | 王老师 |
| S002 | 数据结构 | 李老师 |
| S003 | 操作系统 | 赵老师 |
- 选课成绩表(描述学生和课程的关系)
| 学号(外键) | 课程号(外键) | 成绩 |
|---|---|---|
| 1001 | S001 | 85 |
| 1001 | S002 | 92 |
| 1002 | S003 | 88 |
此时,每张表都满足2NF。选课成绩表中,唯一的非主属性成绩完全依赖于联合主键(学号, 课程号)。
满足3NF(消除传递依赖)
核心:在满足2NF的基础上,消除非主属性之间的传递依赖。即,任何非主属性不能依赖于其他非主属性。
分析我们现有的表:
- 学生表、选课成绩表已满足3NF。
- 问题在课程表:
- 主键是
课程号,非主属性是课程名和教师。 - 看起来没问题?但还有一个字段
教师职称我们之前忽略了。假设我们最初的表格在满足1NF时,把它加回来。那么课程表是这样的:
- 主键是
| 课程号(主键) | 课程名 | 教师 | 教师职称 |
|---|---|---|---|
| S001 | 数据库 | 王老师 | 教授 |
| S002 | 数据结构 | 李老师 | 副教授 |
| S003 | 操作系统 | 赵老师 | 讲师 |
传递依赖:
- 依赖链:
课程号->教师->教师职称。
也就是说,教师职称不是直接由主键课程号决定,而是由非主键教师决定的。(传递依赖!)
解决方案:将与主键无关的属性拆分出去,修改原来的课程表,新增一个教师表。
- 新课程表
| 课程号(主键) | 课程名 | 教师编号(外键) |
|---|---|---|
| S001 | 数据库 | T01 |
| S002 | 数据结构 | T02 |
| S003 | 操作系统 | T03 |
- 教师表
| 教师编号(主键) | 教师 | 教师职称 |
|---|---|---|
| T01 | 王老师 | 教授 |
| T02 | 李老师 | 副教授 |
| T03 | 赵老师 | 讲师 |
现在所有表都满足3NF。
满足 BCNF (消除主属性对非主属性的依赖)
核心:在满足3NF的基础上,所有决定因素都必须是候选键。更直观地说,要消除“主属性被非主属性决定”或“候选键的一部分被非主属性决定”的情况。
我们的3NF表看似完美。为了展示BCNF的违反情况,我们修改一个业务场景:
假设这样一个案例:一位教师只教一门课,但一门课可以有多个学生,一个学生可以选择多门课。
| 学生 | 课程 | 教师 |
|---|---|---|
| 张三 | 数据库 | 王老师 |
| 李四 | 数据库 | 王老师 |
| 张三 | 数据结构 | 李老师 |
分析:
候选键:
(学生, 课程)和(学生, 教师)都可以作为候选键。我们选(学生, 课程)作主键。存在函数依赖:
教师->课程(因为一位教师只教一门课)。
违反BCNF:
决定因素教师-> 课程,但教师不是这个表的超键(仅凭教师无法唯一确定一行,因为一个老师教多个学生)。
问题:如果王老师不教数据库了,需要修改多行数据(更新异常)。
满足BCNF的解决方案:
拆分成两张表,让每个函数依赖的决定因素都是其所在表的超键。
- 教课表(教师是决定因素,作主键)
| 教师(主键) | 课程 |
|---|---|
| 王老师 | 数据库 |
| 李老师 | 数据结构 |
- 选课表
| 学生 | 教师(外键) |
|---|---|
| 张三 | 王老师 |
| 李四 | 王老师 |
| 张三 | 李老师 |
总结
| 范式 | 解决问题 | 拆分动作 |
|---|---|---|
| 1NF | 属性原子性 | 将包含多个值的单元格拆分为多行 |
| 2NF | 消除部分函数依赖 | 将(学号,课程号)为主键的表,拆分为学生实体表、课程实体表和联系表。 |
| 3NF | 消除传递函数依赖 | 从课程表中,将依赖于教师的教师职称属性拆分到独立的教师表。 |
| BCNF | 决定因素必须是候选键 | 在更复杂的“教师-课程-学生”关系中,拆分以确保每个表的决定因素都是超键。 |
最终,通过逐步范式化,我们得到了一个结构清晰、冗余极低、基本无更新异常的数据库模型,通常包含:
- 学生表
- 课程表
- 教师表
- 选课关系表 或 教学关系表
小测验
关系模式R中属性全是主属性,则R的最高范式必定是哪种?
答: 关系模式 R 中属性全是主属性,则 R 的最高范式必定是 3NF,但不一定是 BCNF。
