关系数据理论
问题的提出:为何需要关系数据理论?
在设计关系数据库时,我们常常面临选择:如何将现实世界的信息组织成合适的关系模式?同一个系统可以有多种设计方案,但不同的方案优劣差异很大。
核心问题:
- 如何评价一个关系模式设计的好坏?
- 如何设计出性能良好的关系模式?
「关系数据库的规范化理论」正是为了解决这两个问题而提出的。一个「好」的关系模式应该:
- 减少数据冗余:尽可能少地存储重复信息。
- 避免操作异常:避免在进行插入、删除、更新数据时出现问题。
不良设计的后果
如果关系模式设计不当,通常会导致以下几类问题,统称为操作异常(Anomalies):
- 数据冗余(Redundancy)
- 更新异常(Update Anomaly)
- 插入异常(Insertion Anomaly)
- 删除异常(Deletion Anomaly)
不良模式设计实例分析
我们通过一个简化的学生选课场景来理解这些问题。假设有以下属性:
Sno
:学号Sdept
:学生所在系Mname
:系主任姓名Cno
:课程号Grade
:成绩
现实语义约束:
- 一个学生只属于一个系。
- 一个系有多名学生。
- 一个系只有一名正职系主任。
- 一个学生可选修多门课程,一门课程可被多名学生选修。
- 一个学生修读一门课程只有一个成绩。
设计方案 1:单一关系模式
将所有属性放入一个关系模式 Student(Sno, Sdept, Mname, Cno, Grade)
。
根据语义,我们有以下函数依赖(后续会详细定义):
Sno -> Sdept
:学号确定系Sdept -> Mname
:系确定系主任(Sno, Cno) -> Grade
:学号和课程号确定成绩
该方案存在的问题:
- 数据冗余:
- 学生每次选课,其
Sdept
和Mname
都会被存储一次。如果一个学生选了 10 门课,他的系和系主任信息就重复存储了 10 次。 - 同一个系的所有学生的
Mname
信息都会重复。 - 这不仅浪费存储空间,更重要的是为数据不一致埋下隐患。
- 学生每次选课,其
- 更新异常:
- 如果某系更换了系主任,需要修改该系所有学生所有选课记录中的
Mname
值。如果遗漏了某些记录,就会导致数据不一致。
- 如果某系更换了系主任,需要修改该系所有学生所有选课记录中的
- 插入异常:
- 如果一个系(如新成立的「信息安全系」,系主任「王必成」)刚成立,还没有学生选课,我们无法将这个系及其主任的信息存入数据库。因为
(Sno, Cno)
是主码或候选码的一部分,不能为空,而此时没有Sno
和Cno
。 - 同样,如果一个学生刚入学,尚未选任何课程,也无法单独存储该学生的学号和系信息(如果
(Sno, Cno)
是主码)。
- 如果一个系(如新成立的「信息安全系」,系主任「王必成」)刚成立,还没有学生选课,我们无法将这个系及其主任的信息存入数据库。因为
- 删除异常:
- 如果某个学生(如
S8
)只选修了一门课,并且他是其所在系(如「人工智能」)的最后一名学生。当他毕业或退学,删除其选课记录时,可能会导致「人工智能」这个系及其系主任的信息从数据库中丢失。
- 如果某个学生(如
设计方案 2:多个关系模式
将 Student
模式分解为三个关系模式:
S(Sno, Sdept)
:学生信息,FD:Sno -> Sdept
DEPT(Sdept, Mname)
:系信息,FD:Sdept -> Mname
SC(Sno, Cno, Grade)
:选课信息,FD:(Sno, Cno) -> Grade
该方案的优点:
- 数据冗余减少:系主任姓名
Mname
只在DEPT
表中存储一次。学生所在的系Sdept
也只在S
表中存储一次。 - 更新异常解决:更换系主任只需修改
DEPT
表中对应的一条记录。 - 插入异常解决:可以独立地在
DEPT
表中添加新系的信息,也可以在S
表中添加新学生的信息,无需等待选课。 - 删除异常解决:删除
SC
表中的选课记录不会影响S
表中的学生信息或DEPT
表中的系信息。删除S
表中的学生信息也不会影响DEPT
表。
结论
方案 2 明显优于方案 1。产生这些差异的根本原因在于关系模式内部属性之间的依赖关系处理不当。
数据依赖
数据依赖(Data Dependency)是关系内部属性与属性之间的一种约束关系,是现实世界属性间相互联系的抽象,是数据内在的性质,体现了语义。
- 主要类型包括:函数依赖(Functional Dependency, FD)和多值依赖(Multi-Valued Dependency, MVD)。
- 不好的模式设计往往源于存在不合适的数据依赖。
核心思想:
规范化理论的目标就是通过模式分解(Schema Decomposition),将一个低级范式的关系模式转换为若干个高级范式的关系模式集合,以此来消除不合适的数据依赖,减少冗余和操作异常。
函数依赖
函数依赖
设 是一个属性集 上的关系模式, 是 的子集。若对于 的任意一个可能的关系 , 中不可能存在两个元组在 上的属性值相等,而在 上的属性值不等,则称 函数确定 或 函数依赖于 ,记作 。
- 称为这个函数依赖的决定因素(Determinant)。
- 称为这个函数依赖的依赖因素(Dependent)。
理解要点:
- 语义层面:FD 是基于现实世界语义的约束,不是单从某个数据库快照(关系实例)推导出来的。例如,「学号确定姓名」(
Sno -> Sname
)是因为现实世界规定一个学号对应一个学生。 - 实例的作用:关系实例(表中的数据)不能证明一个 FD 存在,但可以证伪一个 FD。如果在实例中发现存在 值相同但 值不同的元组,则 必然不成立。
- 所有实例:一个 FD 成立,意味着它必须在关系模式 的 所有 可能的关系实例上都得到满足。
- 表示:
- 如果 且 ,则记为 。
- 如果 不函数依赖于 ,则记为 。
图形化理解:想象一个二维坐标系, 轴代表属性(集) 的值域, 轴代表属性(集) 的值域。
- 如果 成立,那么对于任意一个 值,最多只能对应一个 值(类似数学函数的定义)。
- 如果 ,那么至少存在一个 值,对应了多个不同的 值。
函数依赖的类型
非平凡函数依赖 vs. 平凡函数依赖
- 非平凡 FD:若 ,且 ,则为非平凡 FD。例如
Sno -> Sdept
。 - 平凡 FD:若 ,且 ,则为平凡 FD。例如
(Sno, Cno) -> Sno
。
平凡 FD 总是成立,没有实际意义。我们通常关注和讨论的是「非平凡 FD」。
完全函数依赖 vs. 部分函数依赖
设 中有 ,且 是属性组合。
- 完全函数依赖(Fully Functional Dependency):如果对于 的任何真子集 ,都有 ,则称 对 完全函数依赖。记作 。
- 例:在
SC(Sno, Cno, Grade)
中,(Sno, Cno) -F> Grade
(假设成绩不由学号或课程号单独决定)。
- 例:在
- 部分函数依赖(Partial Functional Dependency):如果存在 的一个真子集 ,使得 ,则称 对 部分函数依赖。记作 。
- 例:在
SLC(Sno, Sdept, Sloc, Cno, Grade)
中,键为(Sno, Cno)
。因为Sno -> Sdept
,所以Sdept
部分依赖于(Sno, Cno)
,即(Sno, Cno) -P> Sdept
。
- 例:在
传递函数依赖
设 中有 。
如果 (Y 不能反过来决定 X),且 (Z 不被 Y 包含,即 是非平凡的),则称 对 传递函数依赖。记为 。
例如在 Student(Sno, Sdept, Mname, ...)
中,Sno -> Sdept
, Sdept -> Mname
。假设 Sdept -/> Sno
(一个系有多个学生),且 Mname
不是 Sdept
的一部分。则 Mname
传递依赖于 Sno
。
为何区分依赖类型?
部分函数依赖和传递函数依赖是导致关系模式冗余和操作异常的主要原因之一。规范化过程很大程度上就是为了消除这些不良依赖。
码
码是关系数据库中用于唯一标识元组并建立关系间联系的关键概念,其定义与函数依赖密切相关。
码与相关概念
设关系模式为 ,其中 为属性集, 为函数依赖集。设 。
- 超码:如果 (即 能函数确定 中的所有属性),则 是 的一个超码。
- 候选码:如果 是一个超码,并且对于 的任何真子集 , 都不是超码(即 是最小的超码),则 是 的一个候选码,简称码。
- 主码:如果一个关系模式有多个候选码,则选定其中一个作为主码。主码的选择通常基于简洁性、稳定性等因素。
- 全码:如果关系模式的所有属性构成其唯一的候选码,则称该码为全码。
- 主属性:包含在任何一个候选码中的属性。
- 非主属性或非码属性:不包含在任何候选码中的属性。
- 外码:关系模式 中的属性(或属性组) 不是 的码,但 是另一个关系模式 的主码(或候选码),则称 是 的外码。外码用于建立和强制关系之间的联系(参照完整性)。
示例:
S(Sno, Sdept)
:Sno
是候选码(也是主码)。Sno
是主属性,Sdept
是非主属性。SC(Sno, Cno, Grade)
:(Sno, Cno)
是候选码(也是主码)。Sno
,Cno
是主属性,Grade
是非主属性。DEPT(Sdept, Mname)
:Sdept
是候选码(也是主码)。Sdept
是主属性,Mname
是非主属性。- 在
SC
中,Sno
可以是参照S
表主码Sno
的外码。 Student(Sno, Sname, Ssex, Sage, Sdept)
,如果规定Sno
唯一,Sname
也唯一(不允许重名)。- 候选码:
Sno
,Sname
。 - 可选
Sno
为主码。 - 主属性:
Sno
,Sname
。 - 非主属性:
Ssex
,Sage
,Sdept
。
- 候选码:
规范化
规范化是利用形式化的方法(基于函数依赖等),将关系模式分解,使其达到某种「良好」的标准(范式),从而减少数据冗余和操作异常的过程。
范式(Normal Form, NF)
范式是衡量关系模式「良好」程度的标准。不同级别的范式对模式中允许存在的函数依赖有不同的要求。
主要范式(基于 FD):
- 第一范式(1NF):关系模式 的所有属性都是原子的(不可再分的)。这是关系模型的基本要求。
- 第二范式(2NF):在 1NF 基础上,消除非主属性对候选码的部分函数依赖。
- 第三范式(3NF):在 2NF 基础上,消除非主属性对候选码的传递函数依赖。
- BCNF(Boyce-Codd Normal Form):在 3NF 基础上,消除任何属性(包括主属性)对码的部分依赖和传递依赖。更严格地说,要求所有函数依赖的决定因素必须包含候选码(即决定因素必须是超码)。
范式间的关系:
- 满足高一级范式的关系模式必定满足低一级范式。
- 满足低一级范式的关系模式不一定满足高一级范式。
规范化过程:通过模式分解(将一个关系模式分解为多个关系模式),逐步将关系模式从低级范式提升到高级范式。
第一范式(1NF)
定义
如果一个关系模式 的所有属性域都是原子的,即每个属性值都是不可再分的基本数据项,则称 属于第一范式。
1NF 是关系模型的最低要求,非 1NF 的数据库模式不能称为关系数据库。
满足 1NF 只是基础,并不能保证模式是「好」的,如前面分析的 Student(Sno, Sdept, Mname, Cno, Grade)
就满足 1NF,但存在严重问题。
第二范式(2NF)
定义
若关系模式 ,并且每一个非主属性都完全函数依赖于 的任何一个候选码,则称 。
核心要求:消除非主属性对码的部分依赖。
检查步骤:
- 找出 的所有候选码。
- 找出 的所有非主属性。
- 检查每个非主属性是否对每个候选码都存在部分依赖。如果存在任何一个非主属性对任何一个候选码的部分依赖,则 。
示例:关系模式 SLC(Sno, Sdept, Sloc, Cno, Grade)
,候选码为 (Sno, Cno)
。
- 非主属性:
Sdept
,Sloc
,Grade
。 - 函数依赖:
(Sno, Cno) -> Grade
,Sno -> Sdept
,Sdept -> Sloc
(假设学生住处由所在系决定,即一个系的学生住一起),Sno -> Sloc
(由前两个传递得到)。 - 检查:
Grade
完全依赖于(Sno, Cno)
(假设)。Sdept
依赖于Sno
,而Sno
是(Sno, Cno)
的真子集,所以Sdept
部分依赖于(Sno, Cno)
。Sloc
依赖于Sno
,所以Sloc
部分依赖于(Sno, Cno)
。
- 结论:
SLC
不属于 2NF。
不满足 2NF 会导致由部分依赖引起的冗余和操作异常(如 SLC
例子中 Sdept
, Sloc
的冗余存储、更新/插入/删除异常)。
- 解决方法:投影分解。将
SLC
分解为:SC(Sno, Cno, Grade)
:候选码(Sno, Cno)
,非主属性Grade
完全依赖于码,满足 2NFSL(Sno, Sdept, Sloc)
:候选码Sno
,非主属性Sdept
,Sloc
完全依赖于码,满足 2NF
2NF 的局限性
分解到 2NF 后,虽然消除了非主属性对码的部分依赖,但仍可能存在非主属性对码的传递依赖,导致问题。例如,分解后的 SL
仍可能存在冗余(如果 Sdept -> Sloc
)。
第三范式(3NF)
定义
设关系模式 。若 中不存在这样的候选码 、属性组 及非主属性 (),使得 成立,且 (即 不是超码),则称 。
核心要求:消除非主属性对码的传递依赖。
- 等价地说:如果 ,且没有任何非主属性传递依赖于任何候选码,则 。
- 更简洁的判断(对 2NF 模式):检查是否存在非主属性 通过另一个(或一组)非主属性 传递依赖于码 。
检查步骤(基于 2NF):
- 确认 。
- 找出 的所有候选码和非主属性。
- 检查是否存在非主属性 传递依赖于某个候选码 (即存在 ,其中 本身不是超码,且 是非主属性)。如果存在,则 。
示例:上面得到的关系模式 SL(Sno, Sdept, Sloc)
,候选码 Sno
。
- 非主属性:
Sdept
,Sloc
。 - 函数依赖:
Sno -> Sdept
,Sdept -> Sloc
。假设Sdept -/> Sno
。 - 检查:
Sno -> Sdept
,Sdept -> Sloc
,且Sdept
不是候选码(超码),Sloc
是非主属性。存在非主属性Sloc
对码Sno
的传递依赖。 - 结论:
SL
不属于 3NF。
不满足 3NF 会导致由传递依赖引起的冗余和操作异常。
- 解决方法:投影分解。将
SL
分解为:SD(Sno, Sdept)
:候选码Sno
,满足 3NFDL(Sdept, Sloc)
:候选码Sdept
,满足 3NF
此时,原 SLC
模式最终分解为 SC(Sno, Cno, Grade)
, SD(Sno, Sdept)
, DL(Sdept, Sloc)
。这三个模式都达到了 3NF。
3NF 的改进
达到 3NF 后,基本消除了由非主属性引起的部分依赖和传递依赖,大大改善了模式质量。但在某些特殊情况下,3NF 仍可能存在冗余和异常,特别是当依赖关系涉及到主属性时。
BCNF(Boyce-Codd Normal Form)
BCNF 是修正的第三范式,提出了更强的约束。
定义
设关系模式 。若对于 中每个非平凡的函数依赖 (其中 ), 都必定包含 的某个候选码(即 必须是 的一个超码),则称 。
核心要求:任何函数依赖的决定因素都必须是超码。
与 3NF 的关系:
- BCNF 必定是 3NF。
- 3NF 不一定是 BCNF。
- 区别在于:3NF 允许非主属性被非超码的属性(组)决定(只要不是传递依赖),而 BCNF 要求任何属性(包括主属性)都不能被非超码的属性(组)决定。
导致 3NF 但非 BCNF 的情况通常发生在:
- 存在多个候选码。
- 这些候选码有重叠属性。
- 存在一个函数依赖,其决定因素是候选码的一部分(但不是超码),且依赖因素也是主属性。
示例 1:仓库管理
关系模式 Warehouse(仓库名, 管理员, 物品名, 数量)
- 语义:
- 一个仓库可有多个管理员,一个管理员只在一个仓库工作(
管理员 -> 仓库名
)。 - 一个仓库可存放多种物品,一种物品可存放于多个仓库。
- 对于一个仓库中的特定物品,由唯一指定的管理员负责,且有确定数量(
(仓库名, 物品名) -> 管理员
,(仓库名, 物品名) -> 数量
)。
- 一个仓库可有多个管理员,一个管理员只在一个仓库工作(
- 函数依赖:
管理员 -> 仓库名
,(仓库名, 物品名) -> 管理员
,(仓库名, 物品名) -> 数量
。 - 候选码:
(仓库名, 物品名)
和(管理员, 物品名)
。 - 主属性:
仓库名
,管理员
,物品名
。非主属性:数量
。 - 检查 3NF:
数量
完全依赖于两个候选码,没有部分依赖或传递依赖。满足 3NF。
- 检查 BCNF:
- FD
管理员 -> 仓库名
。决定因素管理员
不是超码(它不能决定所有属性,如物品名
和数量
)。
- FD
- 结论:
Warehouse
属于 3NF,但不属于 BCNF。
仍存在冗余(管理员和仓库名的配对可能重复)和操作异常(如更换仓库管理员可能需要修改多条记录)。
- 解决方法:分解为 BCNF。
R1(管理员, 仓库名)
:FD:管理员 -> 仓库名
,码管理员
,满足 BCNFR2(管理员, 物品名, 数量)
:FD:(管理员, 物品名) -> 数量
,码(管理员, 物品名)
,满足 BCNF
示例 2:学生-教师-课程
关系模式 STJ(S, T, J)
(S: 学生, T: 教师, J: 课程)
- 语义:
- 每个教师只教一门课(
T -> J
)。 - 每门课有若干教师。
- 一个学生选修一门课,有唯一对应的教师(
(S, J) -> T
)。 - 一个学生跟一个老师,也确定了课程(
(S, T) -> J
,由前两个 FD 可推导)。
- 每个教师只教一门课(
- 函数依赖:
T -> J
,(S, J) -> T
。 - 候选码:
(S, J)
和(S, T)
。 - 主属性:
S
,T
,J
。没有非主属性。 - 检查 3NF:由于没有非主属性,自动满足 3NF。
- 检查 BCNF:
- FD
T -> J
。决定因素T
不是超码(不能决定S
)。
- FD
- 结论:
STJ
属于 3NF,但不属于 BCNF。
解决方法:分解为 BCNF。
TJ(T, J)
:FD:T -> J
,码T
,满足 BCNFST(S, T)
:无非平凡 FD,码(S, T)
,满足 BCNF
BCNF 的意义
如果一个关系数据库中的所有关系模式都达到了 BCNF,那么在函数依赖的范畴内,可以说模式已经实现了彻底的分解,达到了最高的规范化程度,从理论上消除了由函数依赖引起的插入和删除异常以及更新异常。