7数据库系统概念(原书第7版)

关系数据库设计

规范化理论

函数依赖范式规范化反规范化

第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 验证范式

sql
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 只是理论上的完美",你同意这个观点吗?请说明理由。