在关系数据库设计中,1NF(第一范式)、2NF(第二范式)、3NF(第三范式)的核心目标是消除数据冗余和避免操作异常(插入、删除、更新异常),它们的区别在于对“函数依赖”的约束强度逐级增强。以下从定义、核心要求、反例(不满足该范式的表)、正例(满足该范式的表)四个维度,结合同一业务场景详细说明:
基础概念铺垫
在举例前,先明确两个关键术语(后续案例会反复用到):
- 函数依赖:若属性A的值能唯一确定属性B的值,则称“B依赖于A”(记为 A→B)。例如“学号→姓名”(一个学号对应唯一姓名)。
- 候选码:能唯一标识一条记录的最小属性集(如“学号+课程号”可唯一标识一条选课记录);主属性是候选码包含的属性,非主属性是候选码外的属性(如“成绩”“姓名”)。
1. 第一范式(1NF):属性原子化,不可再分
核心要求
关系表中的每个属性必须是原子值(不可分割的最小单位),不能是集合、数组、嵌套结构等。
1NF是数据库设计的“入门要求”,不满足1NF的表甚至不能称为“关系表”。
反例(不满足1NF):学生选课表1
| 学号 |
姓名 |
选课信息(课程号+课程名+成绩) |
| 2023001 |
张三 |
(001, 数学, 90), (002, 英语, 85) |
| 2023002 |
李四 |
(001, 数学, 88), (003, 物理, 92) |
问题:“选课信息”属性是“课程号+课程名+成绩”的集合,可再分,违反1NF。
导致的问题:无法单独查询“张三的数学成绩”,只能读取整个“选课信息”后拆分,操作效率极低。
正例(满足1NF):学生选课表2
| 学号 |
姓名 |
课程号 |
课程名 |
成绩 |
| 2023001 |
张三 |
001 |
数学 |
90 |
| 2023001 |
张三 |
002 |
英语 |
85 |
| 2023002 |
李四 |
001 |
数学 |
88 |
| 2023002 |
李四 |
003 |
物理 |
92 |
改进:将“选课信息”拆分为“课程号”“课程名”“成绩”三个原子属性,每个属性不可再分,满足1NF。
2. 第二范式(2NF):消除非主属性对候选码的“部分函数依赖”
核心要求
- 首先满足1NF;
- 消除非主属性对候选码的部分依赖(即非主属性必须依赖于候选码的“全部”,而不是“部分”)。
关键判断:若候选码是“单一属性”(如“学号”),则天然满足2NF(因为不存在“部分依赖”);若候选码是“复合属性”(如“学号+课程号”),则需检查非主属性是否依赖于候选码的某一个部分。
反例(满足1NF,但不满足2NF):学生选课表2(同1NF正例)
| 学号 |
姓名 |
课程号 |
课程名 |
成绩 |
| 2023001 |
张三 |
001 |
数学 |
90 |
| 2023001 |
张三 |
002 |
英语 |
85 |
| 2023002 |
李四 |
001 |
数学 |
88 |
| 2023002 |
李四 |
003 |
物理 |
92 |
第一步:确定候选码和非主属性
- 候选码:
(学号, 课程号)(只有同时知道“学号”和“课程号”,才能唯一确定一条选课记录的“成绩”);
- 主属性:学号、课程号;
- 非主属性:姓名、课程名、成绩。
第二步:分析函数依赖
- 正常依赖(依赖于候选码全部):
(学号, 课程号) → 成绩(必须同时知道学号和课程号,才能确定成绩);
- 部分依赖(问题所在):
学号 → 姓名(仅需候选码的“学号”部分,就能确定姓名,与课程号无关);
课程号 → 课程名(仅需候选码的“课程号”部分,就能确定课程名,与学号无关)。
导致的问题(数据冗余+操作异常)
- 数据冗余:张三的姓名重复存储(选几门课就存几次),数学的课程名也重复存储;
- 更新异常:若张三改姓名,需修改所有包含“2023001”的记录,漏改则数据不一致;
- 插入异常:若新增一门课程(如004,语文),但暂无学生选课,因缺少“学号”(候选码的一部分),无法插入课程信息;
- 删除异常:若删除李四的所有选课记录(如001和003),则“李四”的姓名信息也会被同时删除,无法单独保留学生基本信息。
正例(满足2NF):拆分表为3个表
表1:学生表(候选码:学号,单一属性,天然满足2NF)
| 学号 |
姓名 |
| 2023001 |
张三 |
| 2023002 |
李四 |
表2:课程表(候选码:课程号,单一属性,天然满足2NF)
| 课程号 |
课程名 |
| 001 |
数学 |
| 002 |
英语 |
| 003 |
物理 |
表3:选课表(候选码:(学号, 课程号),非主属性仅“成绩”)
| 学号 |
课程号 |
成绩 |
| 2023001 |
001 |
90 |
| 2023001 |
002 |
85 |
| 2023002 |
001 |
88 |
| 2023002 |
003 |
92 |
改进:通过“拆分表”消除部分依赖——将“姓名”放入“学生表”(依赖于学号),“课程名”放入“课程表”(依赖于课程号),“选课表”仅保留候选码(学号, 课程号)和非主属性“成绩”(依赖于候选码全部),满足2NF。
3. 第三范式(3NF):消除非主属性对候选码的“传递函数依赖”
核心要求
- 首先满足2NF;
- 消除非主属性对候选码的传递依赖(即非主属性不能依赖于“另一个非主属性”,再间接依赖于候选码)。
关键判断:若非主属性A依赖于非主属性B,且B依赖于候选码,则A传递依赖于候选码,违反3NF。
反例(满足2NF,但不满足3NF):扩展学生表(新增“学院”和“学院院长”)
表1:学生表(候选码:学号,满足2NF,但不满足3NF)
| 学号 |
姓名 |
学院 |
学院院长 |
| 2023001 |
张三 |
计算机学院 |
王教授 |
| 2023002 |
李四 |
数学学院 |
李教授 |
| 2023003 |
王五 |
计算机学院 |
王教授 |
第一步:确定候选码和非主属性
- 候选码:学号(单一属性,满足2NF);
- 主属性:学号;
- 非主属性:姓名、学院、学院院长。
第二步:分析函数依赖
- 正常依赖:
学号 → 姓名、学号 → 学院;
- 传递依赖(问题所在):
学号 → 学院 → 学院院长(“学院院长”不直接依赖于候选码“学号”,而是依赖于非主属性“学院”,再间接依赖于“学号”,属于传递依赖)。
导致的问题
- 数据冗余:“计算机学院”的院长“王教授”重复存储(所有该学院的学生记录都存一次);
- 更新异常:若计算机学院换院长(如换成张教授),需修改所有“计算机学院”学生的记录,漏改则数据不一致;
- 插入异常:若新增一个“化学学院”,但暂无学生,因缺少“学号”(候选码),无法插入“化学学院”和其院长信息;
- 删除异常:若删除所有“数学学院”的学生记录,“数学学院”和“李教授”的信息也会被删除,无法单独保留学院信息。
正例(满足3NF):再次拆分表为2个表
表1:学生表(仅保留直接依赖于学号的属性)
| 学号 |
姓名 |
学院 |
| 2023001 |
张三 |
计算机学院 |
| 2023002 |
李四 |
数学学院 |
| 2023003 |
王五 |
计算机学院 |
表2:学院表(候选码:学院,非主属性“学院院长”直接依赖于学院)
| 学院 |
学院院长 |
| 计算机学院 |
王教授 |
| 数学学院 |
李教授 |
| 化学学院 |
赵教授 |
改进:通过拆分表消除传递依赖——将“学院院长”从“学生表”中剥离,放入“学院表”,“学院院长”直接依赖于“学院”(候选码),“学生表”中的“学院”直接依赖于“学号”(候选码),无传递依赖,满足3NF。
三者核心区别总结表
| 范式 |
核心约束(基于1NF) |
解决的问题 |
依赖类型限制 |
举例中的关键改进 |
| 1NF |
属性原子化,不可再分 |
数据不可拆分导致的查询低效 |
无(仅要求原子性) |
将“选课信息”拆分为“课程号+课程名+成绩” |
| 2NF |
消除非主属性对候选码的部分依赖 |
复合候选码下的冗余与异常 |
非主属性必须依赖候选码全部 |
拆分“学生选课表”为“学生表+课程表+选课表” |
| 3NF |
消除非主属性对候选码的传递依赖 |
非主属性间接依赖导致的冗余 |
非主属性不能依赖非主属性 |
拆分“学生表”为“学生表+学院表” |
关键结论
- 范式等级越高,数据冗余越少,操作异常越少,但表的数量可能越多(需通过外键关联查询);
- 实际设计中,3NF是最常用的标准(满足3NF的表已能解决绝大多数冗余和异常问题),更高范式(如BCNF)仅在特殊场景(多候选码交叉依赖)中使用;
- 所有范式的本质是“通过拆分表,让每个属性只依赖于它的直接主属性(候选码)”。