Skip to content

第四章 数据库设计与规范化理论

📖 ⏱️ 预计阅读时长

1. 数据库设计概述

数据库设计是指在给定的应用环境下,根据用户需求,在特定的数据库管理系统上构建最优的数据库模式,建立数据库及其应用系统,使之能够有效地收集、存储、管理和处理数据,满足各类用户的应用需求。数据库设计是整个信息系统开发过程中至关重要的一环,其质量直接影响系统的运行效率、数据的完整性和系统的可维护性。

1.1 数据库设计的基本步骤

按照规范化的设计方法,完整的数据库设计通常分为以下六个阶段,每个阶段有其明确的目标和产出物。

第一阶段是需求分析。这是整个设计过程的基础和起点。设计人员需要深入用户的业务现场,通过调研、访谈和问卷等方式,全面了解用户的数据需求(需要存储哪些数据)、处理需求(数据将如何被使用和加工)、安全性需求(哪些数据需要保密)和完整性需求(数据之间应满足什么约束关系)。此阶段的主要产出物是数据字典和数据流图 (DFD)。数据字典详细记录了每个数据项的名称、含义、类型、取值范围和数据量估计等元信息。数据流图则以图形化的方式描述了数据在系统中的流转、加工和存储过程。这些文档是后续所有设计工作的唯一合法依据,其质量直接决定了后续设计的成败。

第二阶段是概念结构设计。这一阶段的任务是将需求分析得到的结果抽象为一个不依赖于具体 DBMS 的概念模型。概念模型独立于计算机硬件和软件,是现实世界到信息世界的第一层抽象。最常用的概念建模工具是 E-R 模型。概念结构设计的产出物是全局 E-R 图。

第三阶段是逻辑结构设计。这一阶段的任务是将概念模型转换为特定 DBMS 所支持的数据模型(通常是关系模型),形成数据库的逻辑模式。在此过程中需要运用规范化理论对关系模式进行优化,消除数据冗余和操作异常。逻辑设计还包括为各种用户或应用定义合适的外模式(视图)。

第四阶段是物理结构设计。这一阶段需要为逻辑模式选择合适的物理存储结构和存取方法。具体工作包括确定数据的存储方式(如行存储还是列存储)、建立合适的索引(选择索引列、索引类型和索引组织方式)、确定数据的分区策略以及配置数据库系统的各种运行参数(如缓冲池大小、并发连接数等)。物理设计的目标是在满足功能需求的前提下,使系统具有最佳的时间和空间性能。

第五阶段是数据库实施。包括使用 DDL 语句创建数据库结构、编写数据加载程序将初始数据装入数据库、编写和调试应用程序,以及进行系统联调和试运行。

第六阶段是数据库运行和维护。数据库投入正式运行后,需要持续进行性能监控和调优、安全性审计、数据备份与恢复演练,以及在业务需求发生变化时对数据库模式进行适当的调整和扩充。

这六个阶段看似是顺序展开,实际上往往会反复迭代。一个设计方案很少能在第一次讨论时就完全正确,因为真实业务中的很多概念在最初并不清晰。例如“活跃用户”“订单完成时间”“有效成绩”“当前部门”这类术语,表面上人人都懂,真正落到字段定义和统计口径时却常常出现分歧。也正因为如此,需求分析阶段不能只停留在“收集字段”,而必须同步澄清关键术语的业务定义、计算口径和边界条件。若语义不清,后续即使表结构写得再工整,也只是在错误问题上做规范化。

1.2 数据库设计的方法论特点

数据库设计既涉及技术层面(如数据模型、索引设计、查询优化等),也涉及管理层面(如业务流程分析、组织协调等)。在实际项目中,数据库设计通常不是一次性完成的,而是需要经历多轮迭代。随着对应用理解的加深和原型系统运行中问题的发现,设计方案会不断被修正和完善。

因此,数据库设计最常见的两种误区都需要避免。第一种是“唯范式论”,认为范式越高就一定越先进,于是不分场景地不断拆表;第二种是“唯性能论”,认为只要查询快就可以牺牲结构清晰性,结果把多个事实长期混杂在一起。更稳妥的做法是把规范化视为默认基线,因为它能帮助我们建立清晰、可验证的事实边界;把反规范化视为受控例外,只在确有明确访问模式和成本收益依据时才采用。换句话说,设计不是在理论和工程之间二选一,而是在清晰结构的前提下逐步对齐性能需求。


2. 概念结构设计:E-R 模型

E-R 模型由 P.P. Chen 于 1976 年提出,是目前最广泛使用的概念数据模型。它以实体、属性和联系为基本建模元素,用直观的图形化方式描述现实世界的数据结构。

2.1 E-R 图的基本元素

实体 (Entity):客观存在且能够相互区别的事物。实体可以是具体的对象(如一名学生、一本书),也可以是抽象的概念(如一门课程、一个项目)。在 E-R 图中用矩形表示。

属性 (Attribute):实体所具有的某一特性。用椭圆表示,并用线段与所属的实体相连。能够唯一标识实体实例的属性或属性组合称为该实体的码(即键),在 E-R 图中通过在属性名下加下划线来标注。

属性还可以进一步细分为以下类型:简单属性与复合属性(复合属性由若干简单属性组成,例如地址可以分解为省份、城市和街道);单值属性与多值属性(多值属性可以取多个值,例如一个人的电话号码可能有多个);派生属性(其值可以由其他属性计算得出,例如年龄可以由出生日期计算)。

联系 (Relationship):实体之间或实体内部的关联。用菱形表示,菱形内标注联系名,并用线段分别与相关的实体连接。联系本身也可以具有属性。

2.2 联系的类型

联系按参与实体的比例关系,分为一对一 (1:1)、一对多 (1:n) 和多对多 (m:n) 三种基本类型。确定联系类型是概念设计中的关键步骤,因为不同类型的联系在後续转换为关系表时遵循不同的转换规则。

此外还存在以下特殊情况:同一实体集内部的联系(如员工之间的领导与被领导关系,这属于同一实体自身的一对多联系);涉及三个或更多实体的多元联系(如供应商、零件和项目之间的三元联系,表示某个供应商为某个项目供应了某种零件及其数量)。

2.3 扩展的 E-R 概念

弱实体 (Weak Entity):一个不能仅凭自身的属性来唯一标识的实体。弱实体的存在依赖于另一个实体(称为属主实体或强实体)。弱实体的主键通常由其自身的部分键加上属主实体的主键共同组成。例如,家庭成员是相对于员工的弱实体,家庭成员的标识需要结合员工编号和成员姓名才能唯一确定。

ISA 联系(泛化与特化):表示实体类型之间的分类关系。一个高层实体类型可以按照某种特征细分为若干低层实体类型。例如,员工可以特化为技术人员和管理人员,低层实体继承高层实体的全部属性,并可以拥有自己的特有属性。

2.4 局部 E-R 图的集成

在大型系统中,通常由不同的设计人员分别完成各自负责的业务领域的局部 E-R 图,最后需要将所有局部 E-R 图合并为一个统一完整的全局 E-R 图。合并过程中需要识别和解决三类冲突:属性冲突(同名属性的类型或取值范围不一致)、命名冲突(同义异名或同名异义)和结构冲突(同一事物在不同视图中被抽象为不同的元素类型)。

局部 E-R 图集成之所以困难,根源往往不在画图技术,而在不同部门对同一业务对象的理解并不完全一致。销售部门眼中的“客户”可能更强调成交和线索状态,客服部门眼中的“客户”可能更强调服务记录和投诉历史,财务部门眼中的“客户”又更强调结算主体和开票信息。若不在集成阶段把这些视角统一起来,数据库后续就会出现“一词多义”或“同义多词”的问题,进而影响字段命名、主键选择和统计口径。概念设计真正承担的任务,是在技术实现之前先统一认知边界。


3. 逻辑结构设计:E-R 模型到关系模式的转换

将 E-R 图转换为关系模式是逻辑设计的核心任务。转换规则直接决定了后续数据库表的结构。

3.1 实体的转换

每个实体类型转换为一个独立的关系模式。实体的各属性对应关系的各列,实体的码即为关系的主键。

3.2 联系的转换

一对一联系有两种处理方式。可以将联系合并到参与联系的任意一端的关系中,做法是将另一端的主键以及联系本身的属性作为新列加入该关系。也可以为联系单独建立一个关系模式,但这通常会引入不必要的连接操作,在一对一的情况下一般不推荐。

一对多联系的标准做法是将一端的主键加入多端的关系中作为外键,同时将联系自身的属性也加入多端。这是因为多端的每一个元组只对应一端的一个值,将外键放在多端不会引入冗余。而如果反过来将多端的主键放到一端,由于一端的一个元组可能对应多个多端的值,会导致严重的数据冗余和违反第一范式的问题。

多对多联系必须为联系单独建立一个新的关系模式。新关系的属性包括两端实体主键的组合加上联系本身的属性,新关系的主键为两端主键的联合。

多值属性(例如一个员工可能有多个电话号码)需要单独建一个关系,将该属性和所属实体的主键组合为新关系的属性。


4. 函数依赖与规范化理论

规范化理论是关系数据库设计的理论基石。它提供了一套系统化的方法来分析关系模式中的数据冗余问题,并通过对模式的分解来消除冗余和由冗余引发的操作异常(包括插入异常、删除异常和更新异常)。

4.1 函数依赖的定义与分类

设 R(U) 是属性集 U 上的关系模式,X 和 Y 是 U 的子集。如果对 R 的任意一个可能的关系实例 r,r 中不存在两个元组在 X 上的属性值相同而在 Y 上的属性值不同,则称 X 函数决定 Y,记作 X 到 Y。X 称为决定因素。函数依赖反映的是属性之间存在的语义约束关系。

函数依赖按其强度可以分为:完全函数依赖是指 Y 依赖于整个 X 而不依赖于 X 的任何真子集;部分函数依赖是指 Y 依赖于 X 的同时也依赖于 X 的某个真子集;传递函数依赖是指 X 决定 Y,Y 不决定 X,且 Y 决定 Z,则 Z 传递依赖于 X。

4.2 码的形式化定义

设 K 是关系模式 R(U, F) 中的属性或属性组合。如果 K 能够完全函数决定 U(即 K 的闭包等于 U),则 K 是 R 的一个候选键。从候选键中选定一个作为主键。包含在至少一个候选键中的属性称为主属性;不包含在任何候选键中的属性称为非主属性。

主键选择虽然在形式上只是“从候选键里挑一个”,但在工程上影响非常深远。一个键一旦被外键广泛引用,就会成为整个模式结构的骨架。正因为如此,很多系统会采用“代理键 + 业务唯一键”并存的方式:内部关联依赖稳定、短小、无业务语义的代理键,业务层面再通过学号、订单号、身份证号等自然键上的唯一约束维护真实业务唯一性。这样既能降低结构耦合,又不会丢失业务语义。若把这两类需求混在一个键里,后续一旦业务编码规则调整,代价往往会非常高。

4.3 范式

范式是衡量关系模式规范化程度的标准。各级范式之间存在逐级递进的包含关系。

第一范式 (1NF) 要求关系中每个属性的值都是原子性的、不可再分的。这是关系模型的基本要求。例如,如果一个地址字段中存储了省市区的完整地址字符串,只要不需要单独对省市区进行查询或排序,该字段就满足 1NF。但如果将多个电话号码用逗号分隔存储在同一个字段中,则违反了 1NF。

第二范式 (2NF) 要求在满足 1NF 的基础上,消除非主属性对候选键的部分函数依赖。部分依赖通常出现在主键为联合主键的情况下。例如关系选课成绩(学号、课程号、成绩、学生姓名),主键为(学号、课程号)。其中学生姓名仅依赖于学号而不依赖于课程号,构成部分依赖。解决方法是将其分解为学生(学号、学生姓名)和选课成绩(学号、课程号、成绩)两个关系。

第三范式 (3NF) 要求在满足 2NF 的基础上,消除非主属性对候选键的传递函数依赖。例如关系员工(员工号、部门号、部门名称),其中员工号决定部门号、部门号决定部门名称,因此部门名称通过部门号传递依赖于员工号。传递依赖会导致数据冗余(每个属于同一部门的员工都重复存储了部门名称)和更新异常。解决方法是将其分解为员工(员工号、部门号)和部门(部门号、部门名称)。

BC 范式 (BCNF) 要求在满足 3NF 的基础上,关系中的每一个决定因素都包含候选键。3NF 只限制了非主属性的依赖关系,而 BCNF 进一步消除了主属性之间可能存在的不良依赖。也就是说,如果关系中存在主属性对非码属性的依赖,即使满足 3NF 也不满足 BCNF。

第四范式 (4NF) 处理的对象是多值依赖 (MVD)。多值依赖描述的是一个属性集独立地决定另一个属性集的多个值的情况。例如一位教师同时教授多门课程和参与多个科研项目,且课程和项目之间相互独立。4NF 要求消除非平凡的多值依赖,将其分解为独立的关系。

学习范式时,不能只把它们理解为一串定义和判定题,而要看到它们在解决什么实际问题。规范化真正要解决的是“同一事实被重复表达”所带来的三类经典异常。插入异常意味着某些事实必须依附其他事实才能录入,删除异常意味着删除局部信息时可能误删关键事实,更新异常意味着同一语义要改多处且极易前后不一致。范式分解本质上是在重建事实与存储位置的一一对应关系,让一个事实尽量只有一个权威来源,其余场景通过关联获得。只要把这一点想明白,范式就不再只是考试名词,而会成为判断表结构是否健康的工具。


5. Armstrong 公理系统

Armstrong 公理系统是推导函数依赖的理论基础,已被证明具有正确性(由公理推导出的依赖一定成立)和完备性(所有成立的依赖都可以由公理推导出来)。

三条基本规则为:自反律,若 Y 是 X 的子集,则 X 决定 Y;增广律,若 X 决定 Y,则 XZ 决定 YZ;传递律,若 X 决定 Y 且 Y 决定 Z,则 X 决定 Z。

由此可推导出合并规则(若 X 决定 Y 且 X 决定 Z,则 X 决定 YZ)、分解规则(若 X 决定 YZ,则 X 决定 Y 且 X 决定 Z)和伪传递规则(若 X 决定 Y 且 WY 决定 Z,则 XW 决定 Z)。

5.1 属性集闭包

给定函数依赖集 F,属性集 X 关于 F 的闭包是指所有能从 X 出发根据 F 中的依赖关系推导出来的属性的集合。闭包的计算方法是:从 X 出发,反复扫描 F 中的依赖,若某条依赖的左部已包含在当前结果中,则将其右部加入结果,直到结果不再增大。闭包是判定候选键和验证函数依赖是否成立的核心工具。

5.2 最小函数依赖集

最小函数依赖集(最小覆盖)满足三个条件:每条依赖的右部只包含一个属性;不存在冗余的依赖合(即去掉任何一条依赖会改变闭包);每条依赖的左部不包含多余的属性。


6. 模式分解

为了达到更高的范式要求,通常需要将一个关系模式分解为若干个更小的关系模式。但分解不能随意进行,否则可能导致信息丢失或约束丢失。

6.1 无损连接性

无损连接性要求分解后的各子关系通过自然连接能够精确地还原出原关系中的全部数据。判定方法:对于二分解,如果两个子关系的公共属性能够函数决定其中任一方的差集属性,则该分解是无损的。对于多分解,可以使用 Chase 算法(表格追踪法)进行判定。

无损连接性之所以必须强调,是因为分解的目标本来是减少冗余,而不是丢失信息。如果分解后只能恢复出原数据的一个近似结果,说明结构优化是以牺牲语义完整性为代价换来的,这种分解在数据库设计中是不可接受的。因此,每做一次分解,都应追问一个问题:原来那张表中的事实,是否还能被完整而准确地重建回来。这个问题比“形式上是否更像高范式”更根本。

6.2 保持函数依赖

保持函数依赖要求原关系上的所有函数依赖在分解后的各子关系中仍然得以体现,即各子关系上的函数依赖的并集能够覆盖原来的完整依赖集。这保证了分解后业务规则仍然可以在不进行跨表连接的情况下被检验。

无损连接性和保持函数依赖是两个独立的条件,并不总是能同时满足。在实际设计中,无损连接性通常被视为必须满足的条件,而保持函数依赖则尽量满足。

在工程实践里,可以把模式分解的判断整理成三个连续问题:第一,分解后是否无损;第二,关键依赖是否仍能方便验证;第三,这次分解是否真的显著降低了异常风险。若这三个问题无法清楚回答,往往说明分解只是形式上“更规范”,并没有真正提升系统质量。教材强调理论判定,并不是要把设计做成数学游戏,而是帮助设计者在每次拆表时都能给出可解释的理由。


7. 物理设计简述

逻辑设计完成后,还需要根据具体的应用特点和性能要求进行物理设计。物理设计的核心工作包括:为常用查询条件涉及的列建立合适的索引;对于经常需要一起访问的数据,考虑使用聚簇存储以减少 I/O 次数;在某些读密集的场景下,可以适当进行反规范化处理(即有意引入受控的冗余数据以减少连接操作),但需要在应用层额外维护冗余数据的一致性。

物理设计不能脱离访问模式单独讨论。哪些查询最频繁,哪些字段经常作为过滤条件,哪些排序最常出现,哪些报表属于批处理任务,这些都会直接影响索引、分区、缓存和归档策略。若不先看访问路径,而是机械地给“可能会查”的字段大量建索引,就很容易出现索引很多但真正命中不高、写入反而变慢的情况。物理设计的正确出发点,不是数据库支持什么功能,而是业务最常怎样访问数据。

除了即时查询性能,还要考虑时间维度上的设计需求。很多对象并不只有“当前状态”,还存在重要的历史变化,例如专业调整、岗位变更、订单状态流转、商品价格变化等。若设计时只保留当前值,不保留变化轨迹,系统一旦提出审计、追溯、复盘和统计需求,就会发现原有模式难以支撑。因此,数据库设计往往还需要区分当前状态表、历史表、日志表或带生效时间的版本结构。历史数据不是“以后再说”的附加需求,而常常是设计阶段就应纳入考虑的事实。

以学生选课系统为例,可以很清楚地看到设计闭环。需求分析阶段要先确认学生、课程、教师、学院、成绩这些对象的边界和业务规则;概念设计阶段要用 E-R 图把对象和联系表达清楚;逻辑设计阶段要把多对多联系转换为中间关系,并通过主键、外键和约束落地;规范化阶段要检查是否存在冗余导致的三类异常;物理设计阶段则要围绕学号、课程号、学期和时间范围等高频访问路径配置索引。这个例子说明,数据库设计从来不是单点任务,而是一条从业务语义一路落实到物理访问路径的连续链条。

为了让设计评审更具体,可以使用一套简单清单反复检查:语义是否闭环,核心实体与联系是否定义清楚;约束是否覆盖关键业务规则;高频访问是否有明确路径;历史变化与容量增长是否有预案;备份、审计和权限边界是否可落地。数据库设计真正可靠的标志,不是图画得漂亮,而是在这些问题上都能给出清楚答案。

🔒 会员专属内容

检查登录状态中...