概述
数据库设计是构建高效、可靠数据库系统的核心过程。良好的数据库设计能够消除数据冗余、避免更新异常、保证数据完整性。本章介绍关系数据库设计的核心原则——规范化理论,以及在实际工程中如何权衡规范化与性能。
核心问题
如何组织数据库表结构,使得数据既不冗余又能高效访问?
设计目标
- 消除冗余:每条信息只存储一次
- 避免异常:插入、删除、更新时不会出现数据不一致
- 数据完整性:通过约束保证数据的正确性
- 查询效率:在规范化的基础上优化查询性能
函数依赖
定义
函数依赖(Functional Dependency,FD)是关系数据库中最重要的概念之一。设 为一个关系模式, 和 为属性集,如果对于 的任意实例,只要两个元组在 上的值相同,它们在 上的值也相同,则称 函数决定 ,记作 。
-- 示例:学生表
-- StudentID -> Name, Age, Major
-- 即给定学号,可以唯一确定姓名、年龄、专业函数依赖的性质
| 性质 | 描述 |
|---|---|
| 自反律 | |
| 增广律 | |
| 传递律 | |
| 合并律 | |
| 分解律 |
候选键与超键
- 超键:能够函数决定整个关系的属性集
- 候选键:最小的超键(真子集不再是超键)
- 主键:从候选键中选定的唯一标识符
-- 候选键示例
-- {StudentID} 是超键,也是候选键
-- {StudentID, Name} 是超键,但不是候选键(包含冗余)范式理论
范式(Normal Form,NF)是衡量关系模式规范化程度的标准。从1NF到BCNF,规范化程度递增。
第一范式(1NF)
定义:关系中的每个属性都是原子的、不可再分的。
-- 违反1NF:地址字段包含多个值
CREATE TABLE Bad1NF (
StudentID INT PRIMARY KEY,
Name VARCHAR(50),
Address VARCHAR(200) -- "北京市海淀区中关村大街1号"
);
-- 符合1NF:将地址拆分为原子属性
CREATE TABLE Good1NF (
StudentID INT PRIMARY KEY,
Name VARCHAR(50),
City VARCHAR(50), -- "北京市"
District VARCHAR(50), -- "海淀区"
Street VARCHAR(100) -- "中关村大街1号"
);1NF要求:
- 每个单元格只包含单个值
- 不存在重复的列组
- 每条记录唯一可区分(主键)
- 列具有原子性
第二范式(2NF)
定义:在满足1NF的基础上,不存在部分依赖——非主属性完全依赖于候选键的所有部分。
部分依赖:对于复合键 ,如果 ( 为非主属性),则称 部分依赖于键 。
-- 违反2NF:部分依赖
CREATE TABLE Enrollments (
StudentID INT,
CourseID INT,
StudentName VARCHAR(50), -- 只依赖于StudentID
Grade CHAR(1),
PRIMARY KEY (StudentID, CourseID)
);
-- 问题:StudentName只依赖于StudentID,而不是整个主键
-- 符合2NF:分解为两个表
CREATE TABLE Students (
StudentID INT PRIMARY KEY,
StudentName VARCHAR(50)
);
CREATE TABLE Enrollments (
StudentID INT,
CourseID INT,
Grade CHAR(1),
PRIMARY KEY (StudentID, CourseID),
FOREIGN KEY (StudentID) REFERENCES Students(StudentID)
);第三范式(3NF)
定义:在满足2NF的基础上,不存在传递依赖——非主属性不依赖于其他非主属性。
传递依赖:如果 ,,且 ,,则称 传递依赖于 。
-- 违反3NF:传递依赖
CREATE TABLE Students (
StudentID INT PRIMARY KEY,
StudentName VARCHAR(50),
DepartmentID INT, -- 系别ID
DeptName VARCHAR(100) -- 系别名称:传递依赖于StudentID
);
-- StudentID -> DepartmentID -> DeptName
-- 符合3NF:分解为两个表
CREATE TABLE Students (
StudentID INT PRIMARY KEY,
StudentName VARCHAR(50),
DepartmentID INT,
FOREIGN KEY (DepartmentID) REFERENCES Departments(DepartmentID)
);
CREATE TABLE Departments (
DepartmentID INT PRIMARY KEY,
DeptName VARCHAR(100)
);3NF消除的异常:
- 更新异常:修改某系名称需要更新多条学生记录
- 插入异常:新系尚无学生时无法单独存储系信息
- 删除异常:删除某系所有学生会导致系信息丢失
Boyce-Codd范式(BCNF)
定义:对于每个非平凡的函数依赖 , 必须是超键。
BCNF是3NF的强化版本,要求每一个决定因素都是超键。
-- 违反BCNF的例子
-- 假设:每门课由多名教师教授,每名教师只教授一门课
CREATE TABLE Teaching (
CourseID INT,
TeacherID INT,
TeacherName VARCHAR(50),
PRIMARY KEY (CourseID, TeacherID)
);
-- 函数依赖:
-- TeacherID -> TeacherName (教师ID决定教师姓名)
-- CourseID -> (无) 每门课由多教师教授
-- 问题:TeacherID是候选键的一部分,但不是整个候选键的决定因素3NF与BCNF的区别:
| 方面 | 3NF | BCNF |
|---|---|---|
| 依赖类型 | 允许主属性对非候选键的依赖 | 不允许任何非平凡依赖 |
| 应用场景 | 大多数实际应用 | 存在多重重叠候选键时 |
| 分解保证 | 始终可无损分解 | 始终可无损分解 |
范式分解
无损连接分解
将一个关系分解为多个子关系后,通过自然连接能够恢复原关系。
检验定理:设 分解为 ,若 或 ,则分解是无损的。
-- 示例:学生表分解
-- R(StudentID, Name, DepartmentID, DeptName)
-- 分解为:
-- R1(StudentID, Name, DepartmentID)
-- R2(DepartmentID, DeptName)
-- 检验:R1 ∩ R2 = {DepartmentID}
-- DepartmentID -> DeptName (在R2中)
-- 因此分解是无损的保持依赖
分解后,各子关系上的函数依赖并集应能推出原关系的所有函数依赖。
反规范化
为什么要反规范化?
规范化能够消除数据冗余,但可能导致:
- 查询性能下降:多表JOIN增加开销
- 索引策略复杂:跨表查询需要多个索引
- 实时性要求高:事务型应用需要快速读写
常见的反规范化技术
| 技术 | 描述 | 适用场景 |
|---|---|---|
| 表反规范化 | 合并规范化的表 | 读多写少,频繁查询 |
| 预计算聚合 | 存储计算结果 | 报表、统计查询 |
| 垂直分区 | 按访问模式分割列 | 冷热数据分离 |
| 水平分区 | 按行分割表 | 分片策略 |
-- 反规范化示例:预计算订单总额
-- 规范化设计
CREATE TABLE Orders (
OrderID INT PRIMARY KEY,
CustomerID INT,
OrderDate DATE
);
CREATE TABLE OrderItems (
OrderItemID INT PRIMARY KEY,
OrderID INT,
ProductID INT,
Quantity INT,
Price DECIMAL(10,2)
);
-- 反规范化:添加冗余字段
CREATE TABLE Orders (
OrderID INT PRIMARY KEY,
CustomerID INT,
OrderDate DATE,
TotalAmount DECIMAL(10,2) -- 预计算的总金额
);反规范化最佳实践
- 谨慎权衡:只有在测量证实性能问题后才进行反规范化
- 文档记录:明确标注反规范化字段及其维护责任
- 触发器维护:使用触发器或应用逻辑保证数据一致性
- 定期验证:定期检查数据一致性
数据库设计流程
┌─────────────────┐
│ 需求分析 │
└────────┬────────┘
↓
┌─────────────────┐
│ 概念设计(ER图) │
└────────┬────────┘
↓
┌─────────────────┐
│ 逻辑设计(范式) │
└────────┬────────┘
↓
┌─────────────────┐
│ 物理设计(索引) │
└─────────────────┘
步骤详解
- 需求分析:确定实体、属性、业务规则
- 概念设计:绘制ER图,识别实体关系
- 逻辑设计:转换为关系模式,应用规范化
- 物理设计:选择索引、分区策略
实践指南
设计检查清单
- 所有表都有主键
- 符合1NF(原子性)
- 符合2NF(无部分依赖)
- 符合3NF(无传递依赖)
- 外键关系清晰
- 索引策略合理
- 反规范化有文档记录
常见陷阱
- 过度规范化:追求高范式导致表过多,JOIN频繁
- 忽视未来需求:未考虑业务扩展
- 忽略性能测试:设计未经验证就上线
- 反规范化滥用:为方便而牺牲数据一致性
参考文献
本页面内容基于数据库规范化经典理论整理