【数据库系统原理】第15篇:范式理论(上):1NF至BCNF——消除非主属性对码的传递依赖与部分依赖
目录一、范式理论的命题什么样的关系模式是“好”的二、第一范式1NF原子性的底线三、第二范式2NF告别部分依赖四、第三范式3NF切断传递依赖五、BC范式BCNF当候选码不止一个六、逐级分解的实例全景七、结语范式晋级的设计哲学一、范式理论的命题什么样的关系模式是“好”的第14篇建立的Armstrong公理体系与属性集闭包算法为函数依赖的形式化推理提供了完备的工具箱。然而掌握工具本身并不等于知道该用工具解决什么问题。工具的价值在需求中显现。数据库设计者面临的核心问题是在完成E-R模型向关系模式的转化之后得到的一组关系模式是否“足够好”如果不够好应该朝着什么方向改进“好”的评判标准不能依赖于审美直觉它必须植根于可客观验证的指标。范式理论为此提供了精确的测度一个关系模式的设计质量取决于它能否规避特定类型的存储异常。三种存储异常是衡量设计优劣的共同尺度插入异常当设计者试图向关系中插入一条新记录时由于主码尚未完全确定或某些属性无法提供合法取值导致插入操作被阻止——即使在现实世界中该事实已经存在且合法。例如在未设置独立“院系”关系的设计中新成立的院系因尚无任何教师而无法录入数据库。删除异常当设计者从关系中删除一条记录时除了目标信息被移除之外另有其他完全独立的信息随之永久丢失。例如从“教师-院系”合并表中删除最后一位某院系的教师时该院系的存在信息也一同被抹去。更新冗余同一事实在关系中多处重复存储当该事实发生变化时必须在所有副本处同步更新任何一个遗漏都会导致数据不一致。范式理论的分级体系——1NF、2NF、3NF、BCNF、4NF、5NF——正是针对不同类型的函数依赖和多值依赖逐步收窄允许的依赖模式从而逐级消除更复杂的异常类型。本文将聚焦于1NF至BCNF的晋级路径从最基本的原子性要求到消除非主属性对码的部分依赖和传递依赖最终抵达BCNF这一许多工程实践的默认目标。二、第一范式1NF原子性的底线第一范式的定义是所有范式要求的起点一个关系模式R属于第一范式1NF当且仅当R的每个属性所基于的域都只包含原子值——即每个属性值在逻辑上不可再分关系中的每一个行-列交叉点上有且仅有一个值。这个定义看似朴素到近乎浅显但它确立了一条不可妥协的底线关系中不允许出现“表中表”的结构。一个属性不能包含多个值的列表如一个“联系电话”列包含“138xxxx,139xxxx”也不能包含结构化的子表如一个“家庭成员”列包含姓名与关系的嵌套结构。1NF要求所有数据被平坦化为二维表结构这是关系模型区别于层次模型和网状模型的基本特征。违反1NF的设计在实务中并不罕见——常见于从非结构化数据源导入数据时的初始形态。处理多值属性通常需要将其拆分为独立的关系如将“联系电话”拆分为独立的“学生电话”表包含学号外码和电话号码二者共同构成复合主码。处理复合属性则需要将子属性展开为独立的列如将“地址”拆分为省、市、区、街道四列。1NF是关系模型的最低准入门槛。一个不符合1NF的关系甚至不能被称为关系——它与关系定义中“域必须由原子值组成”的要求直接冲突。然而仅满足1NF的关系模式仍然可能充斥着严重的存储异常必须通过更高层级的范式来约束属性之间的函数依赖结构。三、第二范式2NF告别部分依赖在1NF的基础上第二范式引入了对主码与属性之间依赖关系的第一层约束。2NF的定义涉及两个关键概念主属性与非主属性。设R是一个关系模式R的任意一个候选码中的属性称为主属性不属于任何候选码的属性称为非主属性。这一区分是理解所有高阶范式的基石——范式约束的核心就是规范非主属性与码之间的依赖关系。第二范式的定义R ∈ 2NF当且仅当R ∈ 1NF且R的每个非主属性完全函数依赖于R的每一个候选码。这一定义隐含着2NF所针对的病症部分依赖。如果一个非主属性仅依赖于复合候选码的一部分属性而非整个候选码就构成了部分依赖。部分依赖仅在候选码是复合属性集时才有可能出现——如果候选码是单属性则不可能存在对“部分”码的依赖2NF自动满足。考虑一个违反2NF的典型实例。设关系模式选课-学生信息(学号, 课程编号, 成绩, 学生姓名, 学生院系)该关系的候选码为(学号, 课程编号)。成绩完全依赖于整个候选码——仅知道学号或课程编号都无法确定成绩。然而学生姓名和学生院系仅依赖于学号即候选码的真子集——这就是部分依赖。部分依赖引发的异常触目惊心当一名新生尚未选修任何课程时无法插入其基本信息插入异常因为课程编号作为主码的一部分不能为空当一名学生退选所有课程时其个人信息随最后一门选课记录一同被删除删除异常学生的姓名和院系信息在每一门选课记录中重复存储变更院系时需同步更新所有行更新冗余。2NF的分解策略是直截了当的将部分依赖的属性连同它们所依赖的那部分候选码一起从原关系中分离出去形成一个新的关系。上述关系分解为选课(学号, 课程编号, 成绩)——候选码(学号, 课程编号)成绩完全依赖于此候选码。学生(学号, 学生姓名, 学生院系)——候选码(学号)非主属性完全依赖于整个候选码。分解后的两个关系各自满足2NF。选课关系中不再包含仅依赖学号的属性学生关系中候选码是单属性2NF自动成立。原关系中的所有存储异常随之消除新生可以先行录入学生表退选全部课程不会丢失学生信息学生姓名和院系只需在一处更新。值得注意的是如果一个关系模式的候选码是单属性则它自动满足2NF——因为单属性候选码不可能存在真子集部分依赖在逻辑上无从产生。这解释了为何许多设计良好的关系模式无需刻意考虑2NF设计者在选择主码时已经尽可能采用了单属性码从而自然规避了部分依赖的陷阱。四、第三范式3NF切断传递依赖2NF消除了非主属性对码的部分依赖但并未触及另一种同样致命的依赖结构——传递依赖。一个满足2NF的关系模式仍然可能因为传递依赖的存在而遭受严重的存储异常。第三范式的定义R ∈ 3NF当且仅当R ∈ 2NF且R的每个非主属性都非传递依赖于R的每一个候选码。传递依赖的形式化定义已在第14篇给出如果X → Y成立Y → Z成立但Y → X不成立Y不函数确定X且Z ∉ X则称Z传递依赖于X。在3NF的语境下X是候选码Z是非主属性Y是起中介作用的属性集——通常是另一个非主属性或非主属性集。考虑一个满足2NF却违反3NF的实例员工(工号, 姓名, 部门编号, 部门名称, 部门地点)候选码为{工号}——单属性候选码自动满足2NF。函数依赖包括工号 → {姓名, 部门编号, 部门名称, 部门地点}部门编号 → {部门名称, 部门地点}。注意部门名称和部门地点直接依赖于部门编号非主属性而部门编号又依赖于候选码工号且部门编号→工号不成立一个部门包含多名员工。因此部门名称和部门地点通过部门编号这一“桥梁”传递依赖于候选码工号——这正是3NF所禁止的结构。传递依赖引发的异常与部分依赖同样严重新成立的部门在尚未分配员工时无法录入数据库部门编号作为非主属性可以为NULL但作为部门信息的唯一载体逻辑上不应允许部门独立存在当某部门最后一名员工被调离或删除时部门的存在信息随之消散部门名称和地点在每位该部门员工的记录中重复存储。3NF的分解策略将传递依赖链中的“桥梁”属性此处为部门编号及其决定的属性分离为新的关系桥梁属性在新关系中担任候选码在原关系中仅作为外码保留。上述关系分解为员工(工号, 姓名, 部门编号)——候选码{工号}外码部门编号引用部门关系。部门(部门编号, 部门名称, 部门地点)——候选码{部门编号}。分解后的两个关系各自满足3NF。部门信息的独立存在不再依赖于任何员工的在场插入异常和删除异常随之消除冗余被限制在外码列的唯一出现。3NF与2NF之间的逻辑关系值得强调3NF蕴含2NF。任何满足3NF的关系模式必定满足2NF但反之不成立。范式晋级是逐层收紧约束的过程每一层都在前一层的约束基础之上施加更严格的限制。五、BC范式BCNF当候选码不止一个在绝大多数工程实践中3NF已被视为“足够好”的设计标准。然而当关系模式存在多个复合候选码且这些候选码之间出现属性重叠时3NF可能仍不足以消除所有由函数依赖引发的异常。BC范式正是为填补这一理论缝隙而提出。BC范式的定义Boyce-Codd Normal Form由Raymond Boyce与Edgar Codd于1974年提出R ∈ BCNF当且仅当对于R上成立的每一个非平凡函数依赖X → Y即Y ⊈ XX都是R的一个超码。这个定义形式上比3NF更简洁逻辑上比3NF更严格。它不再区分主属性与非主属性而是对所有非平凡函数依赖施加了一视同仁的硬约束任何能函数确定其他属性的属性集都必须具有超码的身份——即它必须能够唯一标识关系中的每一个元组。如果一个依赖的左部不是超码即使它的右部是主属性BCNF也不接受。考虑一个违反3NF但满足BCNF的经典实例。然而此处我们需要的是3NF已满足但BCNF未满足的实例。设关系模式授课安排(学生, 教师, 课程)该关系的语义约束为每位教师只教授一门课程每门课程可由多位教师教授每位学生可以选择多门课程但每门课程只有一位教师。由此导出候选码(学生, 课程)和(学生, 教师)——两个候选码存在重叠共享学生属性。函数依赖包括教师 → 课程每位教师只教一门课(学生, 课程) → 教师(学生, 教师) → 课程。关系中的所有属性都是主属性因为两个候选码覆盖了全部三个属性因此3NF自动满足——3NF仅约束非主属性而此处无非主属性。然而函数依赖“教师 → 课程”的左部“教师”并非超码它不是候选码也不能唯一标识一个元组——一名教师可以在多个学生的选课记录中出现因此违反了BCNF。教师→课程这一依赖导致的实际异常如果某位教师尚未被任何学生选择其教授的课程信息无法录入插入异常当某教师的所有学生退课后该教师与课程的关联信息随之丢失删除异常当某教师更换所授课程时需在其所有学生的记录中逐一更新更新冗余。BCNF的分解策略将违反BCNF的依赖左部提取为新关系的主码将依赖的左部和右部共同构成新关系。上述关系分解为授课(教师, 课程)——候选码{教师}教师→课程满足BCNF。选课(学生, 教师)——候选码(学生, 教师)该关系上无成立的非平凡函数依赖教师已不再决定课程满足BCNF。分解后的两个关系各自满足BCNF原有异常全部消除。但这一分解带来了一个需要警惕的副作用原关系中(学生, 课程)的组合约束——即“每位学生每门课程只有一位教师”——在分解后的两个关系中无法通过局部约束来表达必须通过跨关系的连接验证。这意味着BCNF分解可能无法保持函数依赖。这一问题将在下一篇中与保持依赖性和无损连接性的判定一同深入探讨。六、逐级分解的实例全景为让读者建立对规范化过程的结构性认知以下呈现一个完整的逐级分解实例。原始关系模式取自高校教学管理场景学生选课(学号, 课程编号, 成绩, 学生姓名, 院系编号, 院系名称, 教师工号, 教师姓名)分析函数依赖从业务语义出发确定以下依赖成立(学号, 课程编号) → 成绩 —— 完全依赖学号 → 学生姓名, 学号 → 院系编号 —— 部分依赖院系编号 → 院系名称 —— 传递依赖通过院系编号教师工号 → 教师姓名 —— 部分依赖教师工号依赖(学号, 课程编号)的部分并非如此。此处的实际函数依赖结构需要谨慎分析设课程编号 → 教师工号每门课由一位教师讲授则教师工号 → 教师姓名且课程编号 → 教师工号 → 教师姓名。这构成传递依赖。1NF检查所有属性均为原子值满足1NF。2NF检查候选码为(学号, 课程编号)。非主属性包括成绩、学生姓名、院系编号、院系名称、教师工号、教师姓名。学生姓名和院系编号仅依赖学号——部分依赖违反2NF。分解至2NF选课(学号, 课程编号, 成绩, 教师工号, 教师姓名)——仍包含部分依赖学生(学号, 学生姓名, 院系编号, 院系名称)进一步分析选课关系候选码(学号, 课程编号)。成绩完全依赖候选码。但教师工号和教师姓名如何依赖若课程编号 → 教师工号且课程编号是候选码的真子集则教师工号部分依赖于候选码——仍违反2NF。若将教师工号视为完全依赖于候选码每门课教师不同则需重新考察函数依赖。此处假设更简单的场景课程编号 → 教师工号教师工号 → 教师姓名。选课关系违反2NF教师工号部分依赖课程编号需要进一步分解选课(学号, 课程编号, 成绩)课程(课程编号, 教师工号, 教师姓名)学生关系中候选码(学号)院系编号 → 院系名称构成传递依赖违反3NF。分解学生(学号, 学生姓名, 院系编号)院系(院系编号, 院系名称)课程关系中候选码(课程编号)教师工号 → 教师姓名构成传递依赖课程编号 → 教师工号 → 教师姓名违反3NF。分解课程(课程编号, 教师工号)教师(教师工号, 教师姓名)最终模式集合全部满足3NF学生(学号, 学生姓名, 院系编号)外码院系编号→院系院系(院系编号, 院系名称)课程(课程编号, 教师工号)外码教师工号→教师教师(教师工号, 教师姓名)选课(学号, 课程编号, 成绩)外码学号→学生课程编号→课程这一系列分解的每一步都精确消除了特定类型的函数依赖异常最终产出了一组结构清晰、冗余最小化的关系模式。这一组模式就是规范化理论所追求的“好的设计”。七、结语范式晋级的设计哲学从1NF到BCNF的晋级路径揭示了数据库设计理论中一条深刻的逻辑链条每一种存储异常的背后都有一个不合规的函数依赖结构每一种范式升级的背后都是对某一类依赖结构的清零。1NF清除的是属性内部的嵌套结构它将数据强行压平为二维表。2NF清除的是非主属性对码的部分依赖它要求每一个非主属性都忠诚于完整的候选码而非其一部分。3NF清除的是非主属性对码的传递依赖它切断非主属性之间的决定关系使它们直接依赖于候选码而非通过其他非主属性间接依赖。BCNF则更进一步将所有有决定力的属性集都提升为超码——不论它是主属性还是非主属性没有超码身份就没有决定资格。然而规范化的晋级并非可以无限追求的绝对目标。每一层分解都将一个关系拆分为多个更小的关系这意味着原本可以在一次单表扫描中完成的查询现在可能需要多表连接。规范化的收益消除异常、减少冗余需要与性能开销连接操作的时间成本进行权衡。在工程实践中3NF或BCNF通常被视为规范化的“足够点”——更高的范式4NF、5NF针对的是更罕见的依赖类型多值依赖和连接依赖它们将在下一篇中展开。此外一个始终伴随分解过程的隐忧是分解后的关系集合是否仍能表达原关系集合的所有约束分解是否可逆这就引出了衡量模式分解质量的两个核心判据——无损连接性与保持依赖性。下一篇将对这两个判据进行严格的算法化探讨为规范化理论画上闭环。