# 冗余和异常(Redundancy and Anomalies)
# 1. 冗余的定义与问题
- 冗余指的是在数据库中存储重复的数据,这些数据会占用额外的存储空间并可能引发一致性问题。
- 冗余的影响:当同样的数据在多个地方存储时,如果在一个地方更新数据却忘记在其他地方同步更新,可能导致数据不一致。
# 2. 异常的类型
冗余数据通常会引发三种主要的数据库操作异常:
- 更新异常(Update Anomaly):
- 由于冗余存在,更新一个数据项时,可能需要在多个位置进行更新。如果忘记更新某些位置,数据库将进入不一致的状态。例如,某个课程的教室变更,如果没有同步更新所有相关记录,教室信息将会不一致。
- 插入异常(Insertion Anomaly):
- 在不良设计的数据库中,某些情况下插入新数据会受到限制。例如,如果一个课程表记录包含课程、教室和学生信息,但在没有学生的情况下无法为课程分配教室,这就是插入异常。
- 删除异常(Deletion Anomaly):
- 删除某些数据时,可能会意外删除其他有用的信息。例如,如果删除了所有选某门课的学生记录,可能会导致该课程本身的信息(如教室)也被删除,尽管课程还没有被取消。
# 3. 解决冗余与异常的方法
- 规范化(Normalization):通过规范化的过程(例如将表格分解为多个更小的表),可以减少或消除冗余数据,从而避免上述异常问题。例如,通过分解表结构来消除不必要的冗余信息,将多个相关信息存储在不同的表中,并通过外键进行关联。
# 函数依赖(Functional Dependencies)
# 1. 函数依赖的定义
非正式定义:函数依赖描述了一个属性的值如何唯一地决定另一个属性的值。例如,给定课程代码(
CourseCode
),就能唯一地确定课程名称(CourseName
)。记作:CourseCode → CourseName
。正式定义:如果属性集 X 的值唯一地决定了属性集 Y 的值,则称为 X 函数依赖于 Y,记作
X → Y
。这意味着对于每个 X 的值,Y 的值只能有一个。因此,X 到 Y 的关系是确定的,并且不会产生多对一的映射。
# 2. 函数依赖的识别
通过语义推理:函数依赖通常通过属性之间的语义关系来确定。例如,在大学数据库中,学号(
StudentID
)可以唯一决定学生的姓名(StudentName
)和年龄(StudentAge
)。这个依赖关系可以通过业务规则和语义来识别。通过数据推理:通过实际数据,可以分析并得出函数依赖关系。例如,通过分析学生和他们的课程数据,可以推导出某些属性之间的函数依赖。然而,仅通过观察数据无法保证找到所有函数依赖,因为函数依赖必须在所有可能的数据实例中都成立。
# 3. 函数依赖的用途
检测冗余:函数依赖在检测数据库中的冗余时非常有用。通过识别哪些属性可以由其他属性唯一确定,我们可以识别冗余信息,并且通过规范化过程消除这些冗余。
规范化:函数依赖是数据库规范化的重要基础。通过函数依赖,可以将表格分解为符合第一范式(1NF)、第二范式(2NF)、第三范式(3NF)或 Boyce-Codd 范式(BCNF)的子表,从而避免冗余和异常。例如,如果我们知道
CourseCode → CourseName
,则可以将课程代码和课程名称存储在一个单独的表中,而不在学生记录中重复存储。
# 4. 函数依赖的示例
- 实例 1:在一个银行系统中,分支名称(
BranchName
)可以唯一确定所在城市(City
)。即BranchName → City
。这表明,对于每个分支,其城市信息是唯一确定的。 - 实例 2:在一个课程数据库中,课程代码(
CourseCode
)可以唯一确定课程名称和学分,即CourseCode → {CourseName, Credits}
。通过这一依赖关系,可以消除冗余并确保数据一致性。
# 总结:
函数依赖用于描述数据库中属性之间的确定性关系,它是规范化过程的基础。通过函数依赖,可以识别冗余并消除异常,从而优化数据库设计,确保数据一致性和完整性。
# 范式(Normal Forms)
# 1. 范式的定义
- 范式是数据库设计中用于评估和改进关系模式的标准,规范化的目标是减少数据冗余和避免更新、插入、删除异常。
# 2. 第一范式 (1NF):
- 要求:所有属性的值必须是原子的,即每个字段都不能包含多个值或集合值。每一个关系都必须符合 1NF。
- 示例:假设有一个学生表,学生的电话存储在一个字段中作为一个列表,这违反了 1NF。为了符合 1NF,应该将电话拆分成多个独立的记录。
# 3. 第二范式 (2NF):
- 要求:在满足 1NF 的基础上,消除部分依赖。即任何非主属性都不应依赖于复合主键的一部分(适用于有复合主键的情况)。
- 解释:如果一个表的主键是由多个属性组成的,则表中的非主属性应该完全依赖于主键的所有部分,而不是其中的一部分。
- 示例:如果我们有一个表,主键是
{CourseCode, StudentID}
,而课程名称只依赖于CourseCode
,那么这是部分依赖,需要将课程信息拆分到单独的表中。
# 4. 第三范式 (3NF):
- 要求:在满足 2NF 的基础上,消除传递依赖。即非主属性不能依赖于另一个非主属性。
- 解释:如果属性 A 依赖于主键,属性 B 又依赖于属性 A,那么 B 传递依赖于主键,这是需要避免的。
- 示例:在一个学生表中,如果
StudentID → DepartmentID
且DepartmentID → DepartmentName
,那么StudentID
传递地依赖于DepartmentName
,应将部门信息拆分到单独的表中。
# 5. BCNF (Boyce-Codd 范式):
- 要求:BCNF 是 3NF 的加强版。对于每一个非平凡的函数依赖关系
X → Y
,X 必须是候选键。 - 解释:BCNF 进一步消除了复杂的依赖关系。它的核心要求是确保所有决定其他属性的属性集必须是候选键的一部分。
- 示例:假设一个表的候选键是
{A, B}
,但存在C → A
的依赖关系,且 C 不是候选键,这违反了 BCNF,因此需要对表进行进一步分解。
# 6. 多值依赖与第四范式 (4NF):
- 要求:在满足 BCNF 的基础上,消除多值依赖。即在表中,如果某个属性组独立地依赖于主键的不同部分,应该将其分解。
- 解释:多值依赖的情况是,某个属性可以有多个独立的值,与主键无关。4NF 要求消除这些多值依赖的关系。
- 示例:在一个学生表中,学生可能同时拥有多个电话号码和多个地址,这种情况会导致多值依赖,因此需要将电话号码和地址分成不同的表。
# 7. 范式化的过程
- 通过识别和消除函数依赖、部分依赖、传递依赖以及多值依赖,将数据库模式分解为更小、更规范的表结构,从而消除冗余并避免异常。
- 该过程通常包括从 1NF 开始,逐步检查是否满足 2NF、3NF、BCNF 和 4NF,通过不断地分解关系来优化设计。
# 多值依赖和第四范式(Multivalued dependencies and 4NF)
# 1. 多值依赖(Multivalued Dependency, MVD)
- 定义:多值依赖是指在某个关系中,如果属性集 Y 和属性集 Z 都依赖于属性集 X,但 Y 和 Z 之间彼此独立,则称 Y 和 Z 对 X 存在多值依赖,记作:
X ↠ Y
。 - 解释:多值依赖通常发生在一种情况下,某个属性集与主键没有直接关联,但与主键的不同部分独立关联。例如,学生可能有多个电话号码和多个邮箱地址,但这两者之间是相互独立的,并且都依赖于学生的 ID。
# 2. 第四范式(4NF)
- 要求:在满足 BCNF 的基础上,第四范式要求消除多值依赖。即一个表不能同时有两个或多个独立于其他属性的多值依赖关系。换句话说,如果存在多值依赖
X ↠ Y
,则 X 必须是候选键。 - 解释:如果一个关系中有多值依赖(即一个属性集可以有多个独立的值),那么这个关系就违反了 4NF。为了达到 4NF,必须将具有多值依赖的属性分解为多个关系。
- 目标:通过消除多值依赖,可以减少数据冗余,避免更新、插入和删除时产生的异常。
# 3. 多值依赖的示例
- 示例:假设有一个学生表,存储学生的姓名、电话号码和邮箱地址。一个学生可能有多个电话号码和多个邮箱地址,而电话号码和邮箱地址是彼此独立的。这种情况下,存在多值依赖,因为电话号码和邮箱地址独立于彼此,只是都依赖于学生 ID:
StudentID ↠ PhoneNumber
StudentID ↠ EmailAddress
为了将表规范化到 4NF,我们需要将电话号码和邮箱地址分成两个独立的关系表,一个表存储学生与电话号码的关系,另一个表存储学生与邮箱地址的关系。这可以避免存储冗余数据并确保数据一致性。
# 4. 消除多值依赖的步骤
- 分解关系:如果一个关系中存在多值依赖,那么可以将其分解为两个或多个关系,以消除这些依赖。例如,将学生与电话号码的关系和学生与邮箱地址的关系分别存储在不同的表中。
- 确保无损分解:在进行分解时,必须确保分解后的关系能够通过自然连接(natural join)重新组合成原始的关系,且不丢失任何信息,这称为无损分解。
# 模式分解
# 1. 模式分解的目的
- 减少冗余:模式分解的主要目的是通过将一个大表拆分为多个更小的表,减少数据冗余。
- 避免异常:分解关系可以避免更新、插入和删除时产生的异常(如更新异常、插入异常和删除异常),从而确保数据的一致性和完整性。
- 保持依赖:分解过程中,要确保分解后的关系仍然保持原来的函数依赖,确保不丢失任何业务规则。
# 2. 无损分解(Lossless Decomposition)
- 定义:无损分解是指将一个关系分解为多个子关系后,通过这些子关系的自然连接操作(natural join)能够重新得到原始关系而不丢失信息。
- 目标:无损分解是模式分解的关键目标之一,确保我们在分解表时不会丢失数据或者引入错误的数据。
- 示例:如果我们有一个关系
R(A, B, C)
,并且满足A → B
,我们可以将关系分解为两个子关系R1(A, B)
和R2(A, C)
。此时,通过R1
和R2
的自然连接可以无损地还原原始关系。
# 3. 保持依赖的分解(Dependency-Preserving Decomposition)
- 定义:保持依赖的分解是指,在分解一个关系时,分解后的所有子关系依然保留了原始关系中的所有函数依赖。
- 目标:在保持依赖的分解中,确保所有原始的业务规则(函数依赖)仍然适用于分解后的关系。如果某些依赖关系在分解后的子关系中无法体现,那么我们可能需要添加额外的机制(如触发器或检查约束)来保证这些依赖。
- 示例:如果我们有一个关系
R(A, B, C)
且函数依赖为A → B
和B → C
,我们可以将关系分解为R1(A, B)
和R2(B, C)
,此时这两个子关系分别保留了A → B
和B → C
依赖。
# 4. 分解为 BCNF
- BCNF 分解:如果一个关系不符合 Boyce-Codd 范式(BCNF),我们可以通过分解它使其符合 BCNF。分解的过程通常包括以下步骤:
- 找到违反 BCNF 的依赖关系。
- 根据依赖关系,将关系分解为两个子关系,一个子关系包含所有违反依赖的属性,另一个子关系包含剩余的属性。
- 示例:如果我们有一个关系
R(A, B, C)
,且A → B
,但B → C
(且 B 不是候选键),我们可以将关系分解为R1(B, C)
和R2(A, B)
,确保每个子关系都符合 BCNF。
# 5. 分解为 4NF
- 4NF 分解:如果一个关系中存在多值依赖且不符合第四范式(4NF),我们可以通过分解关系来消除多值依赖。具体步骤与 BCNF 分解类似,但重点是消除多值依赖。
# 6. 分解过程的注意事项
- 在分解过程中,必须确保两点:
- 无损连接:分解后的关系在自然连接时,能够无损还原原始关系。
- 保持依赖:分解后的关系必须保留原始的所有函数依赖,以确保数据库的完整性和业务规则的正确性。