数据库范式是关系型数据库设计中的一种理论,用于消除数据冗余和更新异常,提高数据的一致性和完整性。根据定义,主要有以下四种范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)以及第四范式(4NF)。
第一范式(1NF):在符合第一范式的关系模式中,每个属性都是不可再分的最小单元,也被称为原子性。简单来说,就是数据表中的每一列(每个字段)都必须具备原子性,不能进一步分解。
第二范式(2NF):在满足第一范式的基础上,消除了非主属性对码的部分函数依赖。也就是说,表中的所有列都必须依赖于主键,而不能有任何一列与主键没有关系。这样的设计能确保一个表只描述一件事情。
第三范式(3NF):必须先满足第二范式,要求表中的每一列只与主键直接相关而不是间接相关。换句话说,表中的每一列只能依赖于主键。
假设有一个学生表(Student),包含以下字段:
学号(StudentID):主键,唯一标识每个学生。
姓名(Name):学生的姓名。
年龄(Age):学生的年龄。
班级(Class):学生所在的班级。
班主任(Teacher):负责该班级的班主任。
根据上述定义,可以对表进行范式分析:
第一范式(1NF):表中的每一列都是不可再分的最小单元,满足原子性要求。因此,Student表已经符合第一范式。
第二范式(2NF):消除非主属性对码的部分函数依赖。在这个例子中,StudentID是主键,其他列都依赖于主键。但是,存在一个问题:Class和Teacher列都依赖于同一个非主属性(Class和Teacher都属于同一个实体“班级”),这违反了第二范式的要求。为了解决这个问题,可以将表拆分为两个表:一个学生表(Student)和一个班级表(Class)。学生表中只包含与学生相关的信息,班级表中包含与班级相关的信息。这样,每个表只描述一件事情,满足第二范式的要求。
第三范式(3NF):必须先满足第二范式,要求表中的每一列只与主键直接相关而不是间接相关。在拆分后的表结构中,学生表中的每个列都只依赖于主键StudentID,而班级表中的每个列也只依赖于主键ClassID。因此,拆分后的表结构满足第三范式的要求。
一个违反第一范式(1NF)的例子:
学生ID(StudentID):主键,唯一标识每个学生。
学生的姓名和年龄(Name, Age):学生的姓名和年龄。
在这个例子中,“学生的姓名和年龄”是一个可以进一步分解的数据项,它包含了两个原子数据项:“学生的姓名”和“学生的年龄”。然而,在表中,这两个数据项却被合并成了一个列,这就违反了第一范式的规定,因为根据定义,每一列都应该是原子的、不可再分的。为了符合第一范式,应该将这个表拆分为两个表,一个用于存储学生的姓名,另一个用于存储学生的年龄。
一个违反第二范式(2NF)的例子:
学生ID(StudentID):主键,唯一标识每个学生。
学生的姓名(Name):学生的姓名。
学生的成绩(Score):学生的成绩。
在这个例子中,“学生的成绩”依赖于主键“学生ID”,但是“学生的成绩”本身又包含了多个属性,例如数学成绩、语文成绩、英语成绩等。这就违反了第二范式的规定,因为根据定义,非主属性必须完全依赖于主键,而不能存在只依赖于部分主键的情况。为了符合第二范式,应该将这个表拆分为两个表,一个用于存储学生的基本信息(包括学生ID和姓名),另一个用于存储学生的成绩信息(包括学生ID和各个科目的成绩)。
一个违反第三范式(3NF)的例子:
学生ID(StudentID):主键,唯一标识每个学生。
学生的姓名(Name):学生的姓名。
学生的成绩(Score):学生的成绩。
学生的班级(Class):学生所在的班级。
班级的班主任(Teacher):负责该班级的班主任。
在这个例子中,“学生的班级”和“班级的班主任”都依赖于主键“学生ID”,但是它们本身又包含了其他属性。这就违反了第三范式的规定,因为根据定义,非主属性必须直接依赖于主键,而不能存在传递依赖的情况。为了符合第三范式,应该将这个表拆分为三个表,一个用于存储学生的基本信息(包括学生ID和姓名),一个用于存储学生的成绩信息(包括学生ID和各个科目的成绩),另一个用于存储班级信息(包括班级ID、班级名称和班主任)。这样,每个表只描述一件事情,并且每个非主属性都直接依赖于主键。
下面我用现实生活中的数据表例子,一步步讲清楚 1NF、2NF、3NF、BCNF、4NF 的区别。
不讲公式,只讲“为什么不行 + 怎么改”。
一、1NF(第一范式):列不可再分
✅ 核心要求
每个字段都必须是“最小不可再分”的原子值
❌ 不符合 1NF 的例子
学生表(错误)
| 学号 | 姓名 | 电话 |
|---|---|---|
| 1 | 张三 | 1380000,1390000 |
❌ 问题:
- “电话”字段里存了 多个值
- 无法按单个电话查询、索引、更新
✅ 符合 1NF 的写法
| 学号 | 姓名 | 电话 |
|---|---|---|
| 1 | 张三 | 1380000 |
| 1 | 张三 | 1390000 |
✅ 每个字段都是 单一值
二、2NF(第二范式):消除“部分函数依赖”
✅ 核心要求
非主键字段必须依赖“整个主键”,而不是主键的一部分
👉 只和联合主键有关
❌ 不符合 2NF 的例子
选课表
| 学号 | 课程号 | 学生姓名 | 课程名称 | 成绩 |
|---|---|---|---|---|
| 1 | C01 | 张三 | 数据库 | 90 |
主键:(学号, 课程号)
❌ 问题:
- 学生姓名 只依赖学号
- 课程名称 只依赖课程号
- 它们 不依赖整个主键
✅ 符合 2NF 的拆分
学生表
| 学号 | 学生姓名 |
|---|---|
| 1 | 张三 |
课程表
| 课程号 | 课程名称 |
|---|---|
| C01 | 数据库 |
选课表
| 学号 | 课程号 | 成绩 |
|---|---|---|
| 1 | C01 | 90 |
✅ 非主键字段 完全依赖主键
三、3NF(第三范式):消除“传递依赖”
✅ 核心要求
非主键字段不能依赖另一个非主键字段
❌ 不符合 3NF 的例子
学生表
| 学号 | 姓名 | 学院 | 学院地址 |
|---|---|---|---|
| 1 | 张三 | 计算机 | 教学楼A |
主键:学号
❌ 问题:
- 学院地址 依赖学院
- 学院 依赖学号
- 存在 传递依赖
✅ 符合 3NF 的拆分
学生表
| 学号 | 姓名 | 学院 |
|---|---|---|
| 1 | 张三 | 计算机 |
学院表
| 学院 | 学院地址 |
|---|---|
| 计算机 | 教学楼A |
✅ 非主键字段 只依赖主键
四、BCNF(巴斯-科德范式):更严格的 3NF
✅ 核心要求
每一个决定因素都必须是候选键
👉 解决“主键之外的字段也能决定别的字段”的问题
❌ 不符合 BCNF 的例子
课程安排表
| 教师 | 课程 | 教材 |
|---|---|---|
| 王老师 | 数据库 | 教材A |
| 王老师 | 数据结构 | 教材B |
假设规则:
- 一个教师只教一门课(教师 → 课程)
- 一门课只能用一本教材(课程 → 教材)
候选键:(教师, 教材)
❌ 问题:
- 教师 → 课程
- 但 教师不是候选键的一部分决定因素
✅ 符合 BCNF 的拆分
教师课程表
| 教师 | 课程 |
|---|---|
| 王老师 | 数据库 |
课程教材表
| 课程 | 教材 |
|---|---|
| 数据库 | 教材A |
✅ 所有决定因素都是 候选键
五、4NF(第四范式):消除多值依赖
✅ 核心要求
一个表里不能有多个“独立多值事实”
❌ 不符合 4NF 的例子
学生兴趣表
| 学号 | 爱好 | 技能 |
|---|---|---|
| 1 | 篮球 | Python |
| 1 | 游泳 | Java |
假设:
- 爱好和技能 互不影响
- 但它们放在同一张表
❌ 问题:
- 产生 组合爆炸
- 数据冗余
✅ 符合 4NF 的拆分
学生爱好表
| 学号 | 爱好 |
|---|---|
| 1 | 篮球 |
| 1 | 游泳 |
学生技能表
| 学号 | 技能 |
|---|---|
| 1 | Python |
| 1 | Java |
✅ 每个表只描述 一个多值事实
六、一句话总结(重点)
| 范式 | 解决什么问题 |
|---|---|
| 1NF | 字段不能再拆 |
| 2NF | 比如有两个主键,一些字段值依赖其中一个主键,另一些字段值依赖另一个主键 |
| 3NF | 比如有一个主键,存在传递依赖的情况, |
| BCNF | 决定因素必须是候选键(见后面备注) |
| 4NF | 一个表有多类数据,组合爆炸,数据冗余 |
候选键 与 主属性
这两个概念是理解 2NF/3NF/BCNF 的基础,用一句话先记住:
候选键 = 能唯一标识一行数据的"最小"字段组合
主属性 = 出现在任何一个候选键里的字段
下面用具体表讲清楚。
一、候选键(Candidate Key)
定义
能唯一确定表中每一行,且不含多余字段(最小性)的字段或字段组合。
两个条件:
- 唯一性:给定这些字段的值,只能找到一行
- 最小性:去掉其中任何一个字段,就不能唯一确定了
例子
学生表
| 学号 | 身份证号 | 姓名 | 年龄 |
|---|---|---|---|
| 1001 | 310xxx | 张三 | 20 |
| 1002 | 320xxx | 李四 | 21 |
问:哪些能当候选键?
学号:能唯一确定一行,且单个字段没法再删 → ✅ 候选键身份证号:也能唯一确定一行 → ✅ 候选键姓名:可能重名 → ❌ 不是(学号, 姓名):能唯一确定,但删掉"姓名"还剩"学号"也能唯一确定 → 不满足最小性 → ❌ 不是候选键
所以这张表有两个候选键:学号 和 身份证号。
再看联合候选键的例子
选课表
| 学号 | 课程号 | 成绩 |
|---|---|---|
| 1001 | C01 | 90 |
| 1001 | C02 | 85 |
| 1002 | C01 | 88 |
学号单独 → 不能唯一确定(1001 出现两次)❌课程号单独 → 不能唯一确定(C01 出现两次)❌(学号, 课程号)→ 能唯一确定,且两个都不能删 → ✅ 候选键
这就是联合候选键(复合候选键)。
二、主键(Primary Key)
从多个候选键里挑一个当主键,用来日常标识行。
上面的学生表,学号 和 身份证号 都是候选键,我们通常选 学号 当主键,身份证号 就叫备用键(Alternate Key)。
主键一定是候选键;候选键不一定是主键(没被选中的那些)。
三、主属性(Prime Attribute)
定义
只要出现在任何一个候选键里的字段,都叫主属性;否则叫非主属性。
注意:是"任何一个候选键",不是"主键"。
例子
学生表:候选键是 学号、身份证号
| 字段 | 是否主属性 | 原因 |
|---|---|---|
| 学号 | ✅ 主属性 | 在候选键里 |
| 身份证号 | ✅ 主属性 | 也在候选键里 |
| 姓名 | ❌ 非主属性 | 不在任何候选键里 |
| 年龄 | ❌ 非主属性 | 不在任何候选键里 |
选课表:候选键是 (学号, 课程号)
| 字段 | 是否主属性 |
|---|---|
| 学号 | ✅ 主属性 |
| 课程号 | ✅ 主属性 |
| 成绩 | ❌ 非主属性 |
四、为什么这两个概念重要?
回到前面讲范式时的关键区别:
- 2NF:非主属性不能只依赖联合主键的一部分
- 3NF:非主属性不能传递依赖候选键
- BCNF:连主属性也不能被非候选键决定
3NF 和 BCNF 的分界线就在"主属性"上:
| 范式 | 管不管主属性 |
|---|---|
| 1NF | 不管 |
| 2NF | 只管非主属性 |
| 3NF | 只管非主属性 |
| BCNF | 主属性也管 |
之前"学生选导师"的例子:
| 学生 | 课程 | 老师 |
|---|---|---|
| 张三 | 数据库 | 李老师 |
候选键是 (学生, 课程) 和 (学生, 老师),所以 学生、课程、老师全都是主属性。
依赖 老师 → 课程 里,课程 是主属性却被非候选键的 老师 决定 → 3NF 不管(它只管非主属性),但 BCNF 要管 → 所以违反 BCNF。
五、一张图总结关系
所有字段
├── 主属性(出现在某个候选键里)
│ ├── 主键字段(选中的那个候选键)
│ └── 备用键字段(其他候选键)
└── 非主属性(不在任何候选键里)
口诀:
- 候选键:能唯一标识 + 不能少字段
- 主键:候选键里挑一个
- 主属性:沾了候选键的边就算
- 非主属性:跟候选键没关系
1986



被折叠的 条评论
为什么被折叠?



