概述

数据库设计是构建高效、可靠数据库系统的核心过程。良好的数据库设计能够消除数据冗余、避免更新异常、保证数据完整性。本章介绍关系数据库设计的核心原则——规范化理论,以及在实际工程中如何权衡规范化与性能。

核心问题

如何组织数据库表结构,使得数据既不冗余又能高效访问?

设计目标

  1. 消除冗余:每条信息只存储一次
  2. 避免异常:插入、删除、更新时不会出现数据不一致
  3. 数据完整性:通过约束保证数据的正确性
  4. 查询效率:在规范化的基础上优化查询性能

函数依赖

定义

函数依赖(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的区别

方面3NFBCNF
依赖类型允许主属性对非候选键的依赖不允许任何非平凡依赖
应用场景大多数实际应用存在多重重叠候选键时
分解保证始终可无损分解始终可无损分解

范式分解

无损连接分解

将一个关系分解为多个子关系后,通过自然连接能够恢复原关系。

检验定理:设 分解为 ,若 ,则分解是无损的。

-- 示例:学生表分解
-- R(StudentID, Name, DepartmentID, DeptName)
-- 分解为:
-- R1(StudentID, Name, DepartmentID)
-- R2(DepartmentID, DeptName)
 
-- 检验:R1 ∩ R2 = {DepartmentID}
-- DepartmentID -> DeptName (在R2中)
-- 因此分解是无损的

保持依赖

分解后,各子关系上的函数依赖并集应能推出原关系的所有函数依赖。

反规范化

为什么要反规范化?

规范化能够消除数据冗余,但可能导致:

  1. 查询性能下降:多表JOIN增加开销
  2. 索引策略复杂:跨表查询需要多个索引
  3. 实时性要求高:事务型应用需要快速读写

常见的反规范化技术

技术描述适用场景
表反规范化合并规范化的表读多写少,频繁查询
预计算聚合存储计算结果报表、统计查询
垂直分区按访问模式分割列冷热数据分离
水平分区按行分割表分片策略
-- 反规范化示例:预计算订单总额
-- 规范化设计
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)  -- 预计算的总金额
);

反规范化最佳实践

  1. 谨慎权衡:只有在测量证实性能问题后才进行反规范化
  2. 文档记录:明确标注反规范化字段及其维护责任
  3. 触发器维护:使用触发器或应用逻辑保证数据一致性
  4. 定期验证:定期检查数据一致性

数据库设计流程

┌─────────────────┐
│  需求分析        │
└────────┬────────┘
         ↓
┌─────────────────┐
│  概念设计(ER图) │
└────────┬────────┘
         ↓
┌─────────────────┐
│  逻辑设计(范式) │
└────────┬────────┘
         ↓
┌─────────────────┐
│  物理设计(索引) │
└─────────────────┘

步骤详解

  1. 需求分析:确定实体、属性、业务规则
  2. 概念设计:绘制ER图,识别实体关系
  3. 逻辑设计:转换为关系模式,应用规范化
  4. 物理设计:选择索引、分区策略

实践指南

设计检查清单

  • 所有表都有主键
  • 符合1NF(原子性)
  • 符合2NF(无部分依赖)
  • 符合3NF(无传递依赖)
  • 外键关系清晰
  • 索引策略合理
  • 反规范化有文档记录

常见陷阱

  1. 过度规范化:追求高范式导致表过多,JOIN频繁
  2. 忽视未来需求:未考虑业务扩展
  3. 忽略性能测试:设计未经验证就上线
  4. 反规范化滥用:为方便而牺牲数据一致性

参考文献


本页面内容基于数据库规范化经典理论整理