Day28-07|6.2.3 关系数据库设计基本理论
Day28-07|6.2.3 关系数据库设计基本理论:在冗余、完整性与性能之间做出可解释的权衡
本文对应《系统架构设计师教程(第2版)》第6章“数据库设计基础知识”6.2.3“关系数据库设计基本理论”。关系数据库设计理论主要围绕函数依赖、候选键、规范化和范式展开,目标是减少不必要的数据冗余,避免插入、删除和更新异常,同时保持数据结构可理解、可维护。高级系统架构师还要进一步判断:在真实系统中是否需要反规范化,如何在事务一致性、查询性能、扩展性和数据治理之间取得平衡。
一、学习目标与考试定位
完成本节后,应能够理解函数依赖、完全函数依赖、部分函数依赖和传递函数依赖;能够求候选键并判断关系模式的规范化程度;掌握第一范式、第二范式、第三范式和 BCNF 的基本思想;能够解释插入异常、删除异常和更新异常;能够分析规范化与反规范化的适用条件;能够在案例题和论文中说明如何根据业务事务、查询模式和质量属性设计关系结构。
考试经常给出一个关系模式和若干函数依赖,要求判断候选键、最高范式或分解结果。也可能给出“学生—课程—教师—成绩”这样的表,让考生指出冗余和异常。答题时先列出属性闭包和依赖关系,再判断范式;工程题则要说明分解是否保持无损连接和依赖,必要时结合索引、缓存、汇总表和同步机制讨论性能。
二、函数依赖
函数依赖描述属性之间的决定关系。对于关系模式 R,若属性集 X 的值能够唯一确定属性集 Y 的值,则称 Y 函数依赖于 X,记作 X → Y。它表达的是业务语义,而不是某一批数据碰巧满足的现象。
例如,在员工关系中,员工编号 → 员工姓名、部门编号;在订单明细中,订单编号和商品编号 → 数量、成交价。若商品编号不能唯一决定成交价,因为不同订单可能有不同价格,就不能写成商品编号 → 成交价。
1. 完全函数依赖
若 X → Y,且 X 的任意真子集都不能决定 Y,则称 Y 完全函数依赖于 X。订单编号和商品编号共同决定明细数量,而订单编号单独不能决定某个商品的数量,通常属于完全函数依赖。
2. 部分函数依赖
若 X → Y,但 X 的某个真子集已经能够决定 Y,则称 Y 部分函数依赖于 X。假设关系以订单编号和商品编号作为主键,但订单编号 → 下单日期,那么下单日期对复合主键存在部分依赖,说明订单信息和明细信息混在了一张表中。
3. 传递函数依赖
若 X → Y,Y → Z,且 Y 不包含 X、Z 不包含 Y,则 X 通过 Y 传递决定 Z。例如员工编号 → 部门编号,部门编号 → 部门名称,因此员工编号传递决定部门名称。传递依赖会把部门信息重复存放在每个员工记录中。
三、候选键与属性闭包
候选键是能够唯一标识元组的最小属性集。求候选键时常用属性闭包:从属性集 X 出发,根据函数依赖不断推导能够确定的属性,最终得到 X⁺。若 X⁺ 包含关系的全部属性,则 X 是超键;若 X 的任意真子集都不能覆盖全部属性,则 X 是候选键。
考试解题可以按照以下步骤:先列出关系全部属性;找出不出现在任何函数依赖右部的属性,这些属性往往必须出现在候选键中;逐步应用函数依赖计算闭包;删除多余属性,得到最小候选键;再检查是否存在其他候选键。工程中还要区分候选键与物理主键:代理键可以作为主键,但业务唯一约束仍需保留。
四、关系模式中的异常
1. 插入异常
当一个关系同时保存多个主题的信息时,新增某一主题必须依赖另一个主题,导致无法单独插入。例如只有新课程但还没有学生选课时,无法向“学生—课程—成绩”表插入课程信息。
2. 删除异常
删除某个元组时,连带删除了本应保留的其他信息。例如删除某课程最后一名选课学生后,课程名称和教师信息也消失。
3. 更新异常
同一事实在多个元组重复出现,修改时必须更新多处;漏改会导致不一致。例如部门名称在每个员工记录中重复,部门改名时只更新部分记录。
规范化通过分解关系减少这些异常,但分解后可能增加连接数量和查询成本,因此不是“分得越细越好”。
五、第一范式(1NF)
第一范式要求关系中的每个属性值都是不可再分的原子值,不能在一个字段中存放重复组、列表或嵌套表。将多个手机号用逗号连接、把多个商品编号放入一个字段、用一列保存多个日期,都可能违反原子性。
原子性是相对于业务操作和模型语义而言的。如果系统只把地址作为展示文本,不需要按省市检索,完整地址可能是一个合适属性;如果需要统计地区和校验行政区,就应进一步拆分。范式判断要依据明确的属性定义和函数依赖,不能机械地按字符串长度判断。
六、第二范式(2NF)
第二范式要求关系满足 1NF,并且所有非主属性完全函数依赖于每一个候选键,不存在部分依赖。第二范式主要针对复合候选键;如果候选键只有一个属性,通常不存在部分依赖问题。
例如选课关系 SC(StudentId, CourseId, StudentName, CourseName, Score),候选键为 StudentId + CourseId。StudentName 只依赖 StudentId,CourseName 只依赖 CourseId,存在部分依赖。可以分解为学生关系、课程关系和选课关系,使学生和课程属性各自只保存一次。
分解后要保留连接所需的键和外键,并验证无损连接。不能只凭“看起来拆开了”就认为设计正确。
七、第三范式(3NF)
第三范式要求关系满足 2NF,并且非主属性不传递依赖于候选键。继续以员工关系为例,若 EmployeeId → DepartmentId,DepartmentId → DepartmentName,则 DepartmentName 传递依赖于 EmployeeId。可以将部门编号和部门名称拆到 Department 关系,员工只保存 DepartmentId。
第三范式通常能较好地减少冗余和更新异常,适合核心事务数据。但它并不保证所有复杂业务都达到最佳性能。高频查询可能需要连接多张表,架构师要在稳定事实与读取效率之间做出明确权衡。
八、BCNF 与更严格的规范化
BCNF 要求对于关系中的每一个非平凡函数依赖 X → Y,X 都必须是超键。BCNF 比 3NF 更严格,有些满足 3NF 的关系可能不满足 BCNF,通常出现在多个候选键相互重叠、业务规则复杂的场景。
更高范式可以进一步消除冗余,但分解可能造成依赖无法直接保持,或者需要更多连接和事务协调。考试中要根据题目给出的函数依赖判断,不要仅凭“有三个字段”推断范式;工程设计中还要考虑业务约束、读写比例、审计和演进成本。
九、无损连接与依赖保持
关系分解必须关注无损连接。无损分解要求将分解后的关系重新连接时,能够准确恢复原关系,不产生额外的伪元组,也不丢失原有信息。一个常见判断思路是:分解关系的公共属性应能作为某个分解关系的键,或能通过函数依赖确定其中一方的全部属性。
依赖保持要求原关系中的函数依赖可以通过分解后的关系约束得到验证,而不必每次连接所有关系。若分解虽然无损,但某个重要业务依赖只能跨表检查,维护成本和事务复杂度都会增加。架构师要把强约束放在合理层次,不能为了范式形式牺牲关键业务不变量。
十、规范化与反规范化
规范化适合减少冗余、保护一致性、支持频繁更新和清晰表达事实。反规范化则通过冗余字段、汇总表、宽表、物化视图、缓存或数据复制减少连接和计算,提高读取性能、报表效率或服务独立性。
反规范化不是“放弃设计”,而是有意识地引入冗余并承担同步责任。使用冗余字段时必须明确权威来源、更新触发、失败补偿、重建方法、对账规则和可接受延迟。核心交易事实通常先保持规范化,在测量到连接或聚合成为瓶颈后,再对特定读路径建立投影或汇总。
可以按以下决策顺序判断:先确认查询是否真的慢,查看执行计划和数据量;再尝试索引、SQL、缓存和分区;若仍无法满足指标,设计局部反规范化;最后验证写入一致性、故障恢复和数据重建。不能为了几毫秒的假设性能提前复制所有字段。
十一、案例分析:订单表的规范化与读模型
某系统将客户姓名、客户等级、商品名称、商品分类和成交价全部复制到订单明细表,查询订单详情很快,但客户改名、商品更名和价格修订后,历史订单出现口径混乱。团队想通过“每天全量刷新订单表”解决一致性。
问题分析
当前表混合了订单事实、客户当前属性、商品当前属性和历史成交快照。部分字段复制可能是为了保留下单时快照,部分复制却只是为了减少连接,没有区分语义。每天全量刷新会覆盖历史事实、产生较大资源消耗和同步窗口,不能解决实时变更和失败重试问题。
改进方案
规范化保存客户、商品、订单、订单明细和分类等权威事实;订单明细中保留成交时商品名称、规格、单价等真正属于订单快照的字段,并明确其不可随当前商品修改。客户当前等级和商品当前名称通过关联查询或读模型获得。对订单详情高频读取,可以构建事件驱动的查询投影,记录来源版本、更新时间和重建方式;通过增量同步、幂等、失败补偿和对账保证可控延迟。这样既保护历史事实,又获得读取性能。
十二、综合知识练习与详细答案
练习一
关系 R(A,B,C,D) 的函数依赖为 A→B、B→C、AC→D。请判断 A 是否为候选键。
参考答案:计算 A 的闭包:由 A→B 得到 B,由 B→C 得到 C,再由 AC→D 得到 D,因此 A⁺={A,B,C,D},A 是超键。A 本身只有一个属性,去掉后为空集不能决定全部属性,因此 A 是候选键。AC→D 在这里不是最小依赖的决定因素,因为 A 已经能够通过传递依赖得到 C 并决定 D。
练习二
关系 SC(StudentId, CourseId, StudentName, CourseName, Score) 的候选键为 StudentId+CourseId。指出违反 2NF 的依赖并给出分解。
参考答案:StudentId→StudentName,CourseId→CourseName,两个非主属性分别只依赖复合键的一部分,存在部分函数依赖,违反 2NF。可分解为 Student(StudentId, StudentName)、Course(CourseId, CourseName) 和 SC(StudentId, CourseId, Score),通过两个外键保持选课关系。
练习三
为什么反规范化后的冗余字段必须有权威来源和重建机制?
参考答案:冗余字段存在多个副本,写入失败、消息丢失、并发顺序或程序缺陷都会造成不一致。权威来源用于决定哪个值可信,更新机制用于传播变化,失败补偿和对账用于发现异常,重建机制用于在投影损坏或规则变化时恢复。没有这些机制,反规范化只是把一致性问题从数据库内部转移到应用和运维中。
十三、论文写作素材
论文中可以这样写:在数据库设计阶段,我根据实体、联系和业务规则整理函数依赖,识别候选键及其稳定性,先采用规范化方法消除部分依赖和传递依赖,减少插入、删除和更新异常;对订单、库存、支付等核心事务数据保持清晰的事实和约束,明确无损连接、外键、唯一性和历史快照语义。
针对高频查询,我不盲目拆分或复制数据,而是先通过执行计划、慢查询和容量测试确认瓶颈,再采用索引、分区、缓存、物化视图或局部反规范化建立专用读模型。每个冗余字段都定义权威来源、同步事件、版本、延迟目标、失败补偿、对账和重建机制,不能让读模型成为新的事实来源。模式、依赖、迁移脚本和读模型进入配置管理,发布前执行数据一致性、性能和恢复验证。
十四、今日复盘
函数依赖表达属性决定关系,候选键通过闭包和最小性确定;1NF 解决属性原子性,2NF 消除复合键的部分依赖,3NF 消除非主属性对键的传递依赖,BCNF 对决定因素提出更严格要求。规范化减少冗余和异常,但可能增加连接;反规范化可以提高读取效率,却必须承担同步、对账、补偿和重建责任。
记忆主线:
先找键和依赖,再判范式和异常;核心事实优先保持一致,性能问题用测量证明,冗余数据必须可追踪、可校验、可重建。
下一篇进入 6.3.1 数据库设计的基本步骤,把需求分析、概念设计、逻辑设计、物理设计、实施和运行维护串成完整流程。