数据库系统原理 · 关系数据理论与模式求精 · 自学总结
本章核心:数据库表不是随便建的,需要通过规范化理论消除冗余和异常,让数据结构既正确又高效。
一、函数依赖定义
1.1 是什么?
函数依赖(Functional Dependency,FD)= 描述属性之间决定关系的约束,是数据库规范化理论的基石。
形式化定义
设 R(U) 是一个关系模式,U 是 R 的属性集合,X 和 Y 是 U 的子集。
如果对于 R 的任意一个关系 r,r 中不可能存在两个元组在 X 上的属性值相等,而在 Y 上的属性值不等,则称 X 函数确定 Y,或 Y 函数依赖于 X,记作 X → Y。
关键术语
| 术语 | 是什么 | 例子(假设学号→姓名) |
|---|---|---|
| 决定因素(Determinant) | 箭头左边的属性集 X | 学号 |
| 被决定因素(Dependent) | 箭头右边的属性集 Y | 姓名 |
| 平凡函数依赖 | Y ⊆ X,即 X→Y 但 Y 是 X 的一部分 | {学号, 姓名} → 姓名 |
| 非平凡函数依赖 | Y ⊄ X,即 Y 不是 X 的子集 | 学号 → 姓名 |
| 完全函数依赖 | Y 依赖于 X 整体,不依赖于 X 的任何真子集 | (学号, 课程号) → 成绩(成绩不单独依赖学号或课程号) |
| 部分函数依赖 | Y 依赖于 X 的某个真子集 | (学号, 课程号) → 姓名(姓名只依赖学号,不依赖课程号) |
| 传递函数依赖 | X → Y,Y → Z,且 Y ↛ X,则 X → Z 是传递依赖 | 学号 → 系名,系名 → 系主任,则学号 → 系主任是传递依赖 |
函数依赖的现实意义
学号 → 姓名 一个学号对应唯一姓名 学号 → 系名 一个学生只属于一个系 (学号, 课程号) → 成绩 一个学生一门课只有一个成绩 系名 → 系主任 一个系只有一个系主任
1.2 为什么要有函数依赖?
函数依赖是发现数据冗余和分析数据结构的数学工具。
| 没有函数依赖的困境 | 函数依赖解决后 |
|---|---|
| 凭感觉建表,不知道哪里冗余 | 用 FD 精确计算属性间的决定关系 |
| 数据冗余凭经验找 | FD 推导能系统性地暴露所有冗余 |
| 不知道一张表拆成几张合适 | FD 指导分解到哪种范式最合适 |
| 改一处数据要改多处 | 消除不良 FD,让每张表只表达一个独立主题 |
核心价值:
数学化描述数据结构—— 不再凭感觉,而是用符号精确表达属性关系。
冗余的探测器—— 不良的函数依赖(部分依赖、传递依赖)就是冗余的根源。
规范化的起点—— 没有 FD,就没有范式和分解。
1.3 怎么用?
如何分析函数依赖
给一张表,找出其中所有的 FD:
学生成绩表:(学号, 姓名, 系名, 系主任, 课程号, 课程名, 成绩)
分析步骤:
单属性决定什么?
学号 → 姓名, 系名, 系主任(一个学生只有一个姓名、一个系)
课程号 → 课程名(一门课只有一个名字)
系名 → 系主任(一个系只有一个系主任)
联合属性决定什么?
(学号, 课程号) → 成绩(一个学生一门课只有一个成绩)
判断完全/部分依赖:
(学号, 课程号) → 姓名:部分依赖(姓名只依赖学号)
(学号, 课程号) → 成绩:完全依赖(必须两个一起才能决定成绩)
判断传递依赖:
学号 → 系名 → 系主任,所以学号 → 系主任是传递依赖
函数依赖集 F 的表示
F = { 学号 → 姓名, 学号 → 系名, 学号 → 系主任, 课程号 → 课程名, 系名 → 系主任, (学号, 课程号) → 成绩, (学号, 课程号) → 姓名, (学号, 课程号) → 系名 }二、范式(Normal Form)
2.1 是什么?
范式 = 关系模式满足某种规范化程度的级别,级别越高,冗余和异常越少。
范式的层级关系
1NF ⊃ 2NF ⊃ 3NF ⊃ BCNF ⊃ 4NF ⊃ 5NF (集合越来越小,要求越来越严格)
| 范式 | 全称 | 核心要求 | 解决什么问题 |
|---|---|---|---|
| 1NF | 第一范式 | 所有属性值不可分(原子性) | 消除表中的"表"(嵌套关系、重复组) |
| 2NF | 第二范式 | 满足 1NF,且不存在非主属性对主码的部分依赖 | 消除部分依赖导致的冗余 |
| 3NF | 第三范式 | 满足 2NF,且不存在非主属性对主码的传递依赖 | 消除传递依赖导致的冗余 |
| BCNF | 巴斯-科德范式 | 满足 3NF,且每一个决定因素都是候选码 | 消除主属性对候选码的部分/传递依赖 |
| 4NF | 第四范式 | 满足 BCNF,且不存在非平凡多值依赖 | 消除多值依赖导致的冗余 |
| 5NF | 第五范式 | 满足 4NF,且不存在连接依赖引起的冗余 | 消除连接依赖导致的冗余 |
各级范式的本质对比
学生成绩表:(学号, 姓名, 系名, 系主任, 课程号, 课程名, 成绩) 1NF:✓ 每个单元格都是原子值 ✗ 但有大量冗余:同一学生的姓名、系名、系主任重复存多次 2NF:消除部分依赖 ✗ (学号, 课程号) 是主码,但"姓名"只依赖"学号" → 拆成:学生(学号, 姓名, 系名, 系主任) + 选课(学号, 课程号, 课程名, 成绩) 3NF:消除传递依赖 ✗ "学号 → 系名 → 系主任",系主任传递依赖于学号 → 再拆成:学生(学号, 姓名, 系名) + 系(系名, 系主任) + 选课(...) BCNF:消除所有不良依赖 检查每个 FD 的决定因素是否都是候选码
2.2 为什么要有范式?
没有规范化的表就是"垃圾堆":
| 问题 | 表现 | 根源 |
|---|---|---|
| 数据冗余 | 同一信息存多次 | 部分依赖、传递依赖 |
| 更新异常 | 改一处漏改别处,数据不一致 | 冗余导致多处存储 |
| 插入异常 | 想插入数据但缺少主码值,插不进去 | 一张表承载多个主题 |
| 删除异常 | 删了一条记录,连带丢失其他信息 | 一张表承载多个主题 |
范式的价值:
分级治理—— 不需要一步到最完美,按需求逐步提升到合适级别。
权衡的艺术—— 不是越高级越好,BCNF 以上可能带来过多的表连接开销。
工业标准—— 大多数业务系统做到 3NF 或 BCNF 就足够了。
2.3 怎么用?
判断一张表属于第几范式
步骤 1:找出所有候选码
表:(学号, 姓名, 系名, 系主任, 课程号, 课程名, 成绩) 候选码:(学号, 课程号)
步骤 2:检查 1NF
所有属性值是否原子?→ 是 →满足 1NF
步骤 3:检查 2NF
非主属性(姓名、系名、系主任、课程名、成绩)是否都完全依赖于候选码?
姓名、系名、系主任只依赖学号 →部分依赖→不满足 2NF
步骤 4:检查 3NF(假设已满足 2NF)
非主属性之间有没有传递依赖?
学号 → 系名 → 系主任 →传递依赖→ 如果不拆,不满足 3NF
步骤 5:检查 BCNF
每个 FD 的决定因素是否都是候选码?
系名 → 系主任:决定因素是"系名",但"系名"不是候选码 →不满足 BCNF
从低范式向高范式提升(分解)
1NF → 2NF:消除部分依赖
原表:(学号, 课程号, 姓名, 系名, 系主任, 课程名, 成绩) 拆分为: R1(学号, 姓名, 系名, 系主任) -- 学号为主码,描述学生信息 R2(课程号, 课程名) -- 课程号为主码,描述课程信息 R3(学号, 课程号, 成绩) -- (学号, 课程号)为主码,描述选课关系
2NF → 3NF:消除传递依赖
R1(学号, 姓名, 系名, 系主任) 中:学号 → 系名 → 系主任(传递依赖) 拆分为: R1a(学号, 姓名, 系名) -- 学号为主码 R4(系名, 系主任) -- 系名为主码
最终达到 3NF 的结构:
学生(学号, 姓名, 系名) -- 2NF+3NF 系(系名, 系主任) -- 3NF 课程(课程号, 课程名) -- 3NF 选课(学号, 课程号, 成绩) -- 3NF
范式的工业权衡
| 场景 | 推荐范式 | 理由 |
|---|---|---|
| 一般 OLTP 业务系统 | 3NF ~ BCNF | 冗余少、更新简单 |
| 读多写少的报表/分析系统 | 2NF 甚至反规范化 | 减少连接,提升查询速度 |
| 数据仓库 | 星型/雪花型(特殊设计) | 为分析优化,有意保留部分冗余 |
三、函数依赖理论
3.1 是什么?
函数依赖理论 = 一套关于函数依赖的推理规则和公理系统,用于从已知的 FD 推导出所有隐含的 FD。
下辖知识点
| 知识点 | 是什么 |
|---|---|
| Armstrong 公理系统 | 一套完备且有效的公理,用于推导 FD |
| 自反律(Reflexivity) | 若 Y ⊆ X,则 X → Y |
| 增广律(Augmentation) | 若 X → Y,则 XZ → YZ |
| 传递律(Transitivity) | 若 X → Y 且 Y → Z,则 X → Z |
| 合并规则 | 若 X → Y 且 X → Z,则 X → YZ |
| 伪传递规则 | 若 X → Y 且 WY → Z,则 WX → Z |
| 分解规则 | 若 X → YZ,则 X → Y 且 X → Z |
| 属性集闭包 X⁺ | 由 X 出发能推导出的所有属性的集合 |
| 最小覆盖(最小依赖集) | 与原始 FD 集等价,但无冗余、无多余属性 |
| 候选码的求解 | 利用闭包判断某属性集是否为候选码 |
Armstrong 公理详解
| 公理 | 含义 | 直观理解 |
|---|---|---|
| 自反律 | 自己决定自己的一部分 | 知道身份证号,当然能推出身份证号+姓名里的"身份证号" |
| 增广律 | 左边加属性,结论也加 | 学号能决定姓名,那(学号, 课程号)能决定(姓名, 课程号) |
| 传递律 | 像数学中的传递性 | 学号→系名,系名→系主任,则学号→系主任 |
属性集闭包 X⁺
定义:由属性集 X 出发,利用 F 中的函数依赖,能够推导出的所有属性的集合。
算法(闭包计算):
初始化:X⁺ = X 循环: 对 F 中每个 FD (A → B) 如果 A ⊆ X⁺ 且 B ⊄ X⁺: X⁺ = X⁺ ∪ B 直到 X⁺ 不再变化
例子:
F = { A → B, B → C, C → D } 求 A⁺: A⁺ = {A} A → B 触发:A⁺ = {A, B} B → C 触发:A⁺ = {A, B, C} C → D 触发:A⁺ = {A, B, C, D} 无更多触发 → A⁺ = {A, B, C, D}3.2 为什么要有函数依赖理论?
手动列举所有 FD 不现实:
| 问题 | 函数依赖理论解决 |
|---|---|
| F 中只写了显式 FD,隐含的 FD 成百上千 | Armstrong 公理系统能完备地推导出所有隐含 FD |
| 不知道某属性集是不是候选码 | 计算闭包,如果 X⁺ = 全部属性,则 X 是超码;再去掉冗余属性得候选码 |
| F 中有冗余规则,影响效率 | 求最小覆盖,去掉冗余 FD 和 FD 左边多余的属性 |
| 判断分解是否保持依赖 | 检查分解后的 FD 集是否等价于原 FD 集 |
核心价值:
完备性—— Armstrong 公理是"完备的",所有逻辑蕴含的 FD 都能被推出来。
有效性—— 推导出的 FD 一定是正确的,不会"推错"。
算法基础—— 候选码求解、最小覆盖、模式分解算法都建立在闭包计算之上。
3.3 怎么用?
用闭包求候选码
R(A, B, C, D, E) F = { AB → C, C → D, D → E } 分析: - A、B 只在左边出现(从未在右边出现)→ 它们必须出现在候选码中 - E 只在右边出现 → 它不可能在候选码中 - C、D 两边都出现 → 待定 尝试 (AB)⁺: AB → C:得 {A, B, C} C → D:得 {A, B, C, D} D → E:得 {A, B, C, D, E} = 全部属性 所以 AB 是超码。检查是否有真子集也是超码: A⁺ = {A},B⁺ = {B} → 都不是超码 所以 AB 是候选码求最小函数依赖集(最小覆盖)
三个步骤:
步骤 1:右边单一化
X → YZ 拆成 X → Y, X → Z
步骤 2:去掉左边多余属性
对 X → Y,检查 X 的每个属性 A: 若 (X - {A})⁺ 包含 Y,则 A 是多余的,去掉步骤 3:去掉冗余的 FD
对每条 FD X → Y: 暂时从 F 中去掉它,得到 F' 若 X⁺(基于 F') 仍包含 Y,则该 FD 冗余, permanently 去掉
例子:
F = { A → B, B → C, A → C, AB → C } 步骤 1:右边已单一化 步骤 2:AB → C,检查 A 是否多余: A⁺ = {A, B, C}(基于 F)包含 C → A 多余 所以 AB → C 简化为 B → C(但 B → C 已存在,这条冗余) 步骤 3:去掉 A → C: F' = { A → B, B → C, AB → C } A⁺ = {A, B, C} 包含 C → A → C 冗余,去掉 去掉 AB → C(因为等价于 B → C 已存在) 最小覆盖:{ A → B, B → C }四、模式分解算法
4.1 是什么?
模式分解 = 将一个关系模式拆分成多个子关系模式,目的是消除不良依赖,提升范式级别。
下辖知识点
| 知识点 | 是什么 |
|---|---|
| 无损连接分解(Lossless Join) | 分解后可以通过自然连接完全恢复原始关系,不丢失信息 |
| 保持依赖分解(Dependency Preservation) | 分解后的子模式上的 FD 并集,逻辑蕴含原模式上的所有 FD |
| 3NF 分解算法 | 保证分解既是无损连接又保持依赖 |
| BCNF 分解算法 | 保证无损连接,但不一定保持依赖 |
| 判定无损连接的表格法 | 用 chase 过程判断一个分解是否无损 |
| 判定保持依赖的方法 | 检查每个原 FD 是否都能被分解后的 FD 集逻辑蕴含 |
无损连接 vs 保持依赖
| 性质 | 含义 | 重要性 |
|---|---|---|
| 无损连接 | 分解后能拼回原来的样子,数据没丢 | 必须满足,否则分解就是破坏性的 |
| 保持依赖 | 分解后还能检查原来的所有约束 | 尽量满足,否则约束检查需要跨表连接,效率低 |
BCNF 分解算法
核心思想:找到违反 BCNF 的 FD(决定因素不是候选码),用这个 FD 进行分解。
算法步骤:
输入:关系模式 R,函数依赖集 F 1. 计算 F 在 R 上的覆盖 2. 若 R 已满足 BCNF(所有 FD 的决定因素都是候选码),返回 {R} 3. 找到违反 BCNF 的 FD:X → A,其中 X 不是 R 的候选码 4. 将 R 分解为: R1 = X ∪ A (包含决定因素和被决定的属性) R2 = R - A (去掉被决定属性) 5. 对 R1 和 R2 递归应用本算法 6. 返回所有分解结果例子:
R(学号, 系名, 系主任) F = { 学号 → 系名, 系名 → 系主任 } 候选码:学号 检查 FD: 学号 → 系名:决定因素"学号"是候选码 → OK 系名 → 系主任:决定因素"系名"不是候选码 → 违反 BCNF! 分解(用 系名 → 系主任): R1 = {系名, 系主任} -- 主码:系名 R2 = {学号, 系名} -- 主码:学号 检查 R1:系名 → 系主任,系名是候选码 → 满足 BCNF ✓ 检查 R2:学号 → 系名,学号是候选码 → 满足 BCNF ✓ 分解完成,且是无损连接。 但保持依赖吗?检查原 FD: 学号 → 系名:在 R2 中 ✓ 系名 → 系主任:在 R1 中 ✓ → 本例中恰好也保持了依赖。3NF 分解算法
核心思想:先求最小覆盖,然后为每个 FD 建一张表,最后检查是否有候选码被遗漏。
算法步骤:
输入:关系模式 R,函数依赖集 F 1. 求 F 的最小覆盖 G 2. 对 G 中每个 FD X → A: 创建一个子模式 Ri = X ∪ A 3. 如果没有任何一个 Ri 包含 R 的候选码: 增加一个子模式 = R 的某个候选码 4. 如果有子模式被另一个包含,去掉被包含的 5. 返回分解结果
特点:
一定保持依赖(每个原 FD 都落在某张子表里)
一定无损连接(因为有候选码作为"桥梁"表)
4.2 为什么要有模式分解算法?
分解不是随意拆,而是有约束条件的:
| 随意分解的问题 | 模式分解算法解决 |
|---|---|
| 拆完发现有些数据拼不回去了 | 保证无损连接 |
| 拆完发现原来的约束没法检查了 | 保证保持依赖 |
| 不知道拆到哪一级该停 | 给出明确的终止条件(满足目标范式) |
| 分解方案不唯一,不知道哪个好 | 给出确定性的算法步骤 |
核心原则:
无损连接是底线—— 宁可不拆,也不能拆了拼不回来。
保持依赖是追求—— 约束最好在单表内就能检查,不要每次都要跨表连接。
4.3 怎么用?
实际工作中做分解的步骤
1. 收集函数依赖 F ↓ 2. 分析候选码 ↓ 3. 判断当前属于第几范式 ↓ 4. 确定目标范式(通常 3NF 或 BCNF) ↓ 5. 应用分解算法 ↓ 6. 验证无损连接和保持依赖 ↓ 7. 评估查询性能(连接开销) ↓ 8. 必要时适度反规范化
BCNF vs 3NF 的选择
| 条件 | 选择 |
|---|---|
| 追求无冗余,允许少量跨表约束检查 | BCNF |
| 必须保持所有依赖,约束检查要高效 | 3NF |
| 原模式有多个候选码互相重叠 | 通常只能到3NF(BCNF 可能无法保持依赖) |
经典例子:无法做到 BCNF 且保持依赖的情况
R(城市, 街道, 邮编) F = { (城市, 街道) → 邮编, 邮编 → 城市 } 候选码:(城市, 街道) 和 (街道, 邮编) 检查 BCNF: 邮编 → 城市:决定因素"邮编"不是候选码 → 违反 BCNF 如果用 BCNF 算法分解(用 邮编 → 城市): R1(邮编, 城市) -- 满足 BCNF R2(街道, 邮编) -- 满足 BCNF 保持依赖吗? (城市, 街道) → 邮编:需要城市+街道才能推出邮编 但城市在 R1,街道在 R2,跨表了! → 分解后无法直接检查 (城市, 街道) → 邮编 → **没有保持依赖** 结论:此例中只能接受 3NF(它有多个重叠候选码),不能强求 BCNF。五、数据库模式求精
5.1 是什么?
数据库模式求精 = 在规范化基础上,结合实际应用需求对表结构进行微调,不是教条地追求最高范式。
下辖知识点
| 知识点 | 是什么 |
|---|---|
| 规范化 vs 反规范化 | 规范化是消除冗余,反规范化是有意引入冗余以提升性能 |
| 水平拆分 | 按行拆分(如按时间分区、按地区分表) |
| 垂直拆分 | 按列拆分(如常用字段放一张表,大文本放另一张表) |
| 冗余字段设计 | 在查询频繁的表中冗余存储关联表的字段 |
| 派生列/计算列 | 存储可以由其他列计算得出的值 |
| 分区表 | 将大表按规则分成多个物理存储单元 |
| 物化视图 | 预先计算并存储查询结果,相当于有冗余的缓存表 |
常见的反规范化手段
| 手段 | 是什么 | 场景 |
|---|---|---|
| 冗余字段 | 在订单表中冗余存"客户姓名"(客户表中也有) | 查询订单时几乎都要显示客户名,减少连接 |
| 派生列 | 在订单表中存"订单总金额"(可由明细行计算) | 报表查询频繁,避免实时计算 |
| 合并表 | 把 1:1 的两张表合并成一张 | 总是一起查询,没必要分开 |
| 拆分表 | 把一张大表拆成多张 | 冷热数据分离,热数据放 SSD |
5.2 为什么要有模式求精?
规范化不是银弹:
| 过度规范化的代价 | 模式求精解决 |
|---|---|
| 表拆得太碎,查询要连接七八张表 | 适度合并,减少连接 |
| 报表查询要实时计算聚合,慢得离谱 | 加派生列或物化视图 |
| 高频查询的字段分布在不同表 | 冗余存储到查询入口表 |
| 单表数据量太大,查询慢 | 水平/垂直拆分 |
核心思想:
规范化保证数据正确—— 写入时不出错。
反规范化优化查询性能—— 读取时更快。
在正确性和性能之间找平衡—— 这就是"求精"的含义。
5.3 怎么用?
反规范化的决策流程
1. 先按 3NF/BCNF 设计规范化的模式 ↓ 2. 识别性能瓶颈(慢查询、高并发读) ↓ 3. 分析瓶颈原因(连接太多?计算太多?单表太大?) ↓ 4. 选择反规范化手段 ↓ 5. 建立数据同步机制(冗余字段如何保持同步?) ↓ 6. 监控数据一致性风险
冗余字段的同步策略
-- 方案1:触发器自动同步 CREATE TRIGGER sync_customer_name AFTER UPDATE ON 客户 FOR EACH ROW BEGIN UPDATE 订单 SET 客户姓名 = NEW.姓名 WHERE 客户ID = NEW.客户ID; END; -- 方案2:应用层双写(写客户时同时更新订单) -- 方案3:定时同步/批处理(T+1 更新,适合报表场景)
垂直拆分例子
原表:文章(文章ID, 标题, 作者, 摘要, 正文, 发布时间, 阅读数) 问题:正文很大(几MB),但列表查询只需要标题、作者、摘要 垂直拆分: 文章概要(文章ID, 标题, 作者, 摘要, 发布时间, 阅读数) -- 常查 文章内容(文章ID, 正文) -- 只在详情页查
水平拆分例子
原表:订单(订单ID, 用户ID, 金额, 时间, 状态) 问题:每年几千万单,单表撑不住 水平拆分: 订单_2024(订单ID, ...) -- 历史归档 订单_2025(订单ID, ...) -- 当前活跃 订单_2026(订单ID, ...) -- 未来预留
物化视图例子
-- 原始查询很慢(要连接+聚合) SELECT 系名, AVG(成绩) AS 平均分 FROM 学生 JOIN 选课 ON ... GROUP BY 系名; -- 建物化视图(有冗余,但查起来飞快) CREATE MATERIALIZED VIEW 系成绩统计 AS SELECT 系名, AVG(成绩) AS 平均分 FROM 学生 JOIN 选课 ON ... GROUP BY 系名; -- 定期刷新 REFRESH MATERIALIZED VIEW 系成绩统计;
六、知识脉络图
关系数据理论与模式求精 │ ├── 函数依赖定义 │ ├── 是什么:属性之间的决定关系(X → Y) │ ├── 为什么:数学化描述数据结构,发现冗余根源 │ └── 怎么用:分析表中的FD,区分完全/部分/传递依赖 │ ├── 范式 │ ├── 是什么:规范化级别(1NF/2NF/3NF/BCNF/4NF/5NF) │ ├── 为什么:消除冗余、避免更新/插入/删除异常 │ └── 怎么用:判断当前范式级别,逐步分解提升 │ ├── 函数依赖理论 │ ├── 是什么:Armstrong公理、闭包、最小覆盖 │ ├── 为什么:完备推导所有FD,系统化求候选码和最小依赖集 │ └── 怎么用:公理推导、闭包算法求候选码、三步法求最小覆盖 │ ├── 模式分解算法 │ ├── 是什么:无损连接分解、保持依赖分解 │ ├── 为什么:分解不能乱拆,必须保证拼得回来、约束能检查 │ └── 怎么用:BCNF算法(可能不保持依赖)/ 3NF算法(保持依赖+无损) │ └── 数据库模式求精 ├── 是什么:规范化基础上的性能优化调整 ├── 为什么:过度规范化导致查询慢,需要反规范化平衡 └── 怎么用:冗余字段、派生列、水平/垂直拆分、物化视图、分区
七、一句话记忆
| 概念 | 一句话 |
|---|---|
| 函数依赖 X→Y | X 定了,Y 就跟着定了 |
| 完全依赖 | 必须 X 整体才能定 Y,少一个都不行 |
| 部分依赖 | X 的一部分就能定 Y,是冗余的信号 |
| 传递依赖 | A→B→C,A 间接定 C,也是冗余的信号 |
| 1NF | 每个格子只放一个值,不能是"一组" |
| 2NF | 消除部分依赖,非主属性必须完全依赖主码 |
| 3NF | 消除传递依赖,非主属性不间接依赖主码 |
| BCNF | 任何决定因素都必须是候选码,更严格 |
| Armstrong 公理 | 自反、增广、传递,三条推所有 FD |
| 闭包 X⁺ | X 能推出的所有属性,算它! |
| 无损连接 | 拆了能拼回来,信息没丢 |
| 保持依赖 | 拆了之后原来的约束还能检查 |
| BCNF 算法 | 找个违反 BCNF 的 FD,用它劈成两半,递归 |
| 3NF 算法 | 最小覆盖 → 每条 FD 一张表 → 补候选码 |
| 反规范化 | 明知有冗余,但为了查得快故意留着 |
总结基于《数据库系统概论》第4/5章知识体系整理
