第7章:关系数据库设计(Database Design and Normalization)
一、导读
1.1 本章学习目标
数据库设计的目标是消除冗余、避免异常。本章讲解函数依赖、范式理论、规范化过程。
本章的核心学习目标包括:
- 理解函数依赖:完全依赖、部分依赖、传递依赖
- 掌握范式理论:1NF、2NF、3NF、BCNF
- 理解规范化过程:从非规范化到 BCNF
- 了解反规范化的权衡
- 掌握属性闭包与候选键求解方法
1.2 为什么数据库设计如此重要
不良的数据库设计会导致:
- 数据冗余:相同数据存储在多个地方,浪费存储空间
- 更新异常:修改数据时需要更新多个地方,容易遗漏导致数据不一致
- 插入异常:无法插入某些数据,例如新部门还没有员工时无法录入部门信息
- 删除异常:删除数据时丢失其他信息,例如删除最后一个员工时部门信息也丢失了
规范化是解决这些问题的系统方法。通过规范化理论,可以系统地分析关系模式的质量,并通过分解达到更高的设计标准。
1.3 核心问题引导
在学习本章时,请思考以下问题:
什么是函数依赖?它与现实世界中的什么概念对应?
为什么需要多个范式?每个范式解决了什么问题?
规范化是否总能保证无损连接和函数依赖保持?
反规范化在什么场景下是合理的?
二、核心概念详解
2.1 函数依赖
函数依赖(Functional Dependency, FD)描述属性之间的决定关系。
2.1.1 定义
如果属性集 X 的值唯一确定属性集 Y 的值,则称 Y 函数依赖于 X,记作 X → Y。
学号 → 姓名
学号 → 系别
(学号, 课程号) → 成绩函数依赖反映的是现实世界的语义约束,不是数据在某一时刻的偶然巧合。
2.1.2 函数依赖的类型
- 完全函数依赖:Y 依赖于 X 的全部,不依赖于 X 的任何真子集。例如 (学号, 课程号) → 成绩,成绩依赖于学号和课程号的组合,不能仅由其中一个决定。
- 部分函数依赖:Y 依赖于 X 的某个真子集。例如在 (学号, 课程号, 姓名) 中,姓名仅依赖于学号,这就是部分依赖。
- 传递函数依赖:X → Y,Y → Z,则 X 传递依赖于 Z。例如学号 → 系别 → 系主任,学号通过系别间接决定系主任。
2.1.3 Armstrong 公理系统
Armstrong 公理是推导函数依赖的完备推理系统:
- 自反律:若 Y ⊆ X,则 X → Y
- 增广律:若 X → Y,则 XZ → YZ
- 传递律:若 X → Y 且 Y → Z,则 X → Z
由此可以推导出合并律、分解律和伪传递律等有用规则。
2.2 范式理论
范式是关系数据库设计的标准。
2.2.1 第一范式(1NF)
所有属性都是原子的(不可再分)。
不符合 1NF:
┌─────┬──────────────┐
│ 学号 │ 电话 │
├─────┼──────────────┤
│ 001 │ 123, 456 │ ← 电话不是原子的
符合 1NF:
┌─────┬──────┐
│ 学号 │ 电话 │
├─────┼──────┤
│ 001 │ 123 │
│ 001 │ 456 │
└─────┴──────┘1NF 是关系模型的基本要求。不满足 1NF 的表不是真正的关系表。
2.2.2 第二范式(2NF)
在 1NF 基础上,消除非主属性对候选键的部分函数依赖。
不符合 2NF:
Student_Course(学号, 课程号, 姓名, 系别, 成绩)
候选键:(学号, 课程号)
部分依赖:学号 → 姓名, 学号 → 系别
分解为 2NF:
Student(学号, 姓名, 系别)
Course(学号, 课程号, 成绩)2NF 确保了每个非主属性完全依赖于整个候选键,而不是候选键的一部分。
2.2.3 第三范式(3NF)
在 2NF 基础上,消除非主属性对候选键的传递函数依赖。
不符合 3NF:
Student(学号, 姓名, 系别, 系主任)
传递依赖:学号 → 系别 → 系主任
分解为 3NF:
Student(学号, 姓名, 系别)
Department(系别, 系主任)3NF 的正式定义:对于每个非平凡函数依赖 X → A,要么 X 包含候选键,要么 A 是主属性(属于某个候选键)。
2.2.4 BCNF(Boyce-Codd 范式)
每个非平凡函数依赖 X → Y,X 都包含候选键。
不符合 BCNF:
Student_Course(学号, 课程号, 教师)
函数依赖:教师 → 课程号(一个教师只教一门课)
候选键:(学号, 课程号)、(学号, 教师)
教师 → 课程号 中,教师不包含候选键
分解为 BCNF:
Teacher_Course(教师, 课程号)
Student_Course(学号, 教师)BCNF 消除了所有基于函数依赖的冗余。如果一个关系模式属于 BCNF,那么对于每个非平凡函数依赖 X → Y,X 一定是超键。
2.3 规范化过程
规范化是将关系模式分解为更高范式的过程。
非规范化 → 1NF → 2NF → 3NF → BCNF每一步分解都保证:
- 无损连接:分解后的关系可以自然连接恢复原关系
- 保持函数依赖:分解后的关系保持原有的函数依赖
需要注意的是,BCNF 的分解不一定能同时保证无损连接和函数依赖保持。而 3NF 的分解总是可以同时保证这两个性质。
2.4 反规范化
有时为了提高查询性能,会故意引入冗余(反规范化)。
反规范化的技术:
- 添加派生属性:存储计算结果,如订单总金额
- 添加冗余属性:复制常用数据,减少 JOIN
- 合并表:减少 JOIN 操作,提高查询速度
反规范化的权衡:
- 优点:提高查询性能,减少 JOIN 开销
- 缺点:增加更新开销、可能导致数据不一致
三、深入分析
3.1 候选键的求解算法
求解候选键的算法:
将所有属性分为四类:
- L 类:只出现在 FD 左部
- R 类:只出现在 FD 右部
- LR 类:出现在 FD 左右两部
- N 类:不出现在 FD 中
候选键必须包含所有 L 类和 N 类属性
从 LR 类属性中选择属性,计算闭包,判断是否为候选键
属性闭包的计算方法:从 X 开始,反复应用函数依赖,直到不再有新属性加入。如果 X 的闭包包含所有属性,则 X 是超键。
3.2 最小函数依赖集
最小函数依赖集满足:
- 每个 FD 的右部只有一个属性
- 没有冗余的 FD(去掉任何一个 FD 都会改变依赖集的语义)
- 每个 FD 的左部没有冗余属性
求最小函数依赖集的步骤:
将 FD 右部分解为单属性
去除左部的冗余属性
去除冗余的 FD
3.3 多值依赖和 4NF
多值依赖(Multivalued Dependency, MVD)描述属性的独立多值关系。
例如,一个教师可以教多门课,也可以有多个研究方向。课程和研究方向之间没有关联,这就是多值依赖:教师 →→ 课程 | 研究方向。
4NF 消除非平凡的多值依赖。如果一个关系模式中存在非平凡的多值依赖 X →→ Y,且 X 不包含候选键,则不满足 4NF。4NF 的分解可以消除多值依赖导致的冗余。
四、实践案例
4.1 案例一:学生选课系统的规范化
初始的非规范化关系模式:
Student_Course_Info(学号, 姓名, 系别, 系主任, 课程号, 课程名, 成绩, 教师)分析函数依赖:
- 学号 → 姓名, 系别
- 系别 → 系主任
- 课程号 → 课程名, 教师
- (学号, 课程号) → 成绩
逐步规范化:
消除部分依赖(达到 2NF):分解为 Student、Course、Enrollment
消除传递依赖(达到 3NF):进一步分解 Student 为 Student 和 Department
最终得到符合 3NF 的五个关系模式,消除了所有冗余和异常。
4.2 案例二:使用 MySQL 验证范式
CREATE TABLE Student (
id INT PRIMARY KEY,
name VARCHAR(50),
dept_id INT,
FOREIGN KEY (dept_id) REFERENCES Department(id)
);
CREATE TABLE Department (
id INT PRIMARY KEY,
name VARCHAR(50),
director VARCHAR(50)
);通过外键约束体现分解后关系之间的联系,保证参照完整性。
4.3 案例三:反规范化的实际应用
在一个电商系统中,订单列表页面需要显示用户名称。如果严格按照 3NF,用户名称存储在用户表中,每次查询订单列表都需要 JOIN 用户表。为了提高查询性能,可以在订单表中冗余存储用户名称,接受更新时需要同时修改的代价。
五、常见误区
5.1 误区一:"范式越高越好"
范式越高,表越多,JOIN 操作越多,查询性能可能下降。实际设计中通常达到 3NF 即可,某些读多写少的场景可以适当反规范化。
5.2 误区二:"规范化可以解决所有问题"
规范化解决的是数据冗余和异常问题,不能解决性能问题。设计数据库时需要同时考虑规范化和性能,在两者之间找到平衡点。
5.3 误区三:"BCNF 分解总能保持函数依赖"
BCNF 的分解不一定能保持所有函数依赖。如果需要同时保证无损连接和函数依赖保持,3NF 是可以保证的最高范式。
5.4 难点:理解 BCNF 和 3NF 的区别
3NF 允许主属性对候选键的部分依赖和传递依赖,BCNF 不允许任何非平凡 FD 的左部不包含候选键。当关系中只有一个候选键且所有属性都是主属性时,3NF 和 BCNF 等价。
5.5 难点:求解候选键
求解候选键需要计算属性闭包,算法比较复杂。建议通过大量练习来掌握这一技能,它是理解范式理论的基础。
六、本章小结
第7章讲解了关系数据库设计:
函数依赖 描述属性之间的决定关系
范式理论 提供数据库设计的标准
规范化过程 将关系模式分解为更高范式
反规范化 为了提高性能引入冗余
BCNF 是最严格的范式
后续章节将讲解应用设计与开发。
七、思考题
解释函数依赖、部分函数依赖和传递函数依赖的区别,各举一个实际例子。
为什么 3NF 的分解总能保证无损连接和函数依赖保持,而 BCNF 不能?
给定关系模式 R(A, B, C, D, E) 和函数依赖集 {A→B, BC→D, D→E},求 R 的所有候选键。
什么是反规范化?在什么场景下应该考虑反规范化?列举三种反规范化的技术。
设计一个图书馆管理系统的数据库模式,要求至少达到 3NF。写出函数依赖分析和规范化过程。
多值依赖与函数依赖有什么区别?为什么需要 4NF?
有人说"只要达到 3NF 就够了,BCNF 只是理论上的完美",你同意这个观点吗?请说明理由。