1 噩梦的开始
维护一个设计有问题的数据库,对任何数据库管理员来说都像是一场噩梦。因为管理好一个带有异常的数据库几乎是不可能的。
数据库的异常通常表现在以下三个方面:
- 更新异常 : 当数据项存在多个副本且没有正确关联时,就可能会出现一致性问题。例如,更新一个拥有多个副本的数据项,一些实例得到正确更新,而另一些缺保留旧值。
- 删除异常 : 尝试删除一条记录,但由于不知情,其中的一部分未被删除,数据还保存在其它地方。
- 插入异常 : 尝试插入重复的记录。
如何识别并解决这些异常?什么样的数据库设计才是正确的?这些正是数据库规范化要解决的问题。
2 数据库规范化
数据库规范化是一种帮助设计最佳数据库模式的技术。数据库规范化的核心思想是将表划分为更小的表,并建立表之间的关联,而不是复制数据。
数据库规范化由一系列的范式(Normal Form)组成,包括第一范式(1NF)、第二范式(2NF)、第三范式(3NF)和Boyce-Codd范式(BCNF)等。
这些范式存在递进关系:后边的范式,在前面范式的基础上,进一步增强了约束条件。
2.1 第一范式
第一范式(1NF)要求关系中的所有属性必须具有原子域,即属性的值是不可分割的。
例如,下表的Content就违反了第一范式:
Course | Content |
---|---|
Programming | Java, C++ |
Web | HTML, PHP, ASP |
将其转换为第一范式:
Course | Content |
---|---|
Programming | Java |
Programming | C++ |
Web | HTML |
Web | PHP |
Web | ASP |
2.2 第二范式
先了解以下概念:
- 主属性:候选键中的某个属性
- 非主属性:不属于任何候选键的某个属性
第二范式(2NF)要求:
- 在满足第一范式的基础上
- 所有非主属性必须完全依赖于候选键
- 即,如果函数依赖X→A成立,那么不能存在X的任何一个真子集Y,使得Y→A也成立
- 如果候选键为单个属性,则一定满足第二范式
以学生-选课表为例:
Stu_ID | Proj_ID | Stu_Name | Proj_Name | Grade |
---|---|---|---|---|
201 | 1 | Alice | Project1 | A |
202 | 1 | Bob | Project1 | A |
201 | 2 | Alice | Project2 | B |
202 | 2 | Bob | Project2 | C |
在这个关系中,{Stu_ID, Proj_ID} 组成了唯一的候选键。考虑到Stu_Name可能重名,所以{Stu_Name, Proj_Name}不是候选键。
我们发现 Stu_name 也可以通过 Stu_ID 来确定,这就是所谓的部分依赖,这在第二范式中是不允许的。
为了消除这种部分依赖,我们需要将学生-选课表分解为三个表:
- 选课表:
Stu_ID | Proj_ID | Grade |
---|---|---|
201 | 1 | A |
202 | 1 | A |
201 | 2 | B |
202 | 2 | C |
- 课程信息表:
Proj_ID | Proj_Name |
---|---|
1 | Project1 |
2 | Project2 |
- 学生信息表:
Stu_ID | Stu_Name |
---|---|
201 | Alice |
202 | Bob |
这样,每个非主属性都完全依赖于候选键,满足了第二范式的要求。
2.3 第三范式
第三范式(3NF)要求:
- 在满足第二范式的基础上
- 每个非主属性都直接依赖于候选键,而不依赖于其他非主属性
- 即候选键和非主属性之间不存在传递依赖,即不存在{候选键}→{非主属性A}→{非主属性B}
以学生-详细信息表为例:
Stu_ID | Stu_Name | City | Zip |
---|---|---|---|
1 | Alice | New York | 10001 |
2 | Bob | Los Angeles | 90001 |
我们发现 Stu_ID 是主键且是唯一的主属性。
可以看出:
- Stu_ID(主键)可以确定 Zip(非主属性),即 {Stu_ID} → {Zip}。
- Zip(非主属性) 可以确定 City(非主属性),即 {Zip} → {City}。
- 这样就形成了传递依赖:{Stu_ID} → {Zip}→ {City}
为了将这个关系转换成第三范式,我们需要将其分解为两个关系,如下所示:
学生-详细信息表:
Stu_ID | Stu_Name | Zip |
---|---|---|
1 | Alice | 10001 |
2 | Bob | 90001 |
城市信息表:
Zip | City |
---|---|
10001 | New York |
90001 | Los Angeles |
这样,每个非主属性都完全依赖于主键,且非主属性之间不存在依赖,满足了第三范式的要求。
2.4 BCNF
Boyce-Codd Normal Form(BCNF)规定:
- 满足第三范式的基础上
- 不允许主属性部分依赖其它候选键
还是以学生-选课表为例:
Stu_ID | Proj_ID | Stu_Phone | Proj_Name | Grade |
---|---|---|---|---|
201 | 1 | 1234 | Project1 | A |
202 | 1 | 5678 | Project1 | A |
201 | 2 | 1234 | Project2 | B |
202 | 2 | 5678 | Project2 | C |
存在函数依赖:
- {Stu_ID, Proj_ID} → {Stu_Phone, Proj_Name, Grade}
- {Stu_Phone, Proj_Name} → {Stu_ID,Proj_ID, Grade}
同时也存在主属性依赖: - {Stu_ID}→{Stu_Phone}
- {Proj_ID}→{Proj_Name}
因此不满足BCNF。
为了将这个关系转换成BCNF,我们需要将其分解为三个关系,如下所示:
- 选课表
Stu_ID | Proj_ID | Grade |
---|---|---|
201 | 1 | A |
202 | 1 | A |
201 | 2 | B |
202 | 2 | C |
- 学生信息表
Stu_ID | Stu_Phone |
---|---|
201 | 1234 |
202 | 5678 |
- 课程信息表
Proj_ID | Proj_Name |
---|---|
1 | Project1 |
2 | Project2 |
2.5 四种范式的关系
- 1NF是数据库规范化的基础,它要求表中的所有字段都具备原子性
- 2NF建立在1NF的基础上,消除非主属性对候选键的部分函数依赖
- 3NF建立在2NF的基础上,消除非主属性对候选键的传递函数依赖
- BCNF是3NF的加强版,消除主属性对候选键的部分函数依赖
2.6 规范等级权衡
并不是数据库规范等级越高就一定越好。
虽然更高的规范化等级可以消除数据冗余和确保数据完整性,但它也可能导致以下缺点:
- 性能下降:规范化后的数据库可能需要更多的表和连接,这可能会降低查询性能
- 复杂性增加:规范化后的数据库可能更复杂,这可能会使设计、维护和理解变得更加困难
因此,在选择数据库规范化等级时,需要权衡优点和缺点:
- 对于需要更严格的数据完整性并且低冗余的应用程序,更适合较高的规范化等级
- 对于需要高性能和低复杂性的应用程序,较低的规范化等级可能是更好的选择
3 总结
数据库规范化的思想与软件设计原则都是相同的,比如:
- DRY(不要重复自己):规范化通过消除冗余来遵守 DRY 原则
- KISS(保持简单,傻瓜):规范化通过将复杂的关系分解为更简单的关系来遵循 KISS 原则
- 高内聚,低耦合:规范化通过将相关数据分组到一个关系中并最小化关系之间的依赖关系来实现高内聚和低耦合
- 一个事实只有一个来源:规范化确保每个事实只存储在一个地方,从而消除数据不一致的风险
- 形式服从功能:规范化优先考虑数据完整性和一致性,而不是数据的物理存储方式
- 逐步细化,持续优化:规范化是一个逐步的过程,每一步都是在前一级别的基础上进一步提高规范化程度,目的是使数据库模式更加精炼和高效
- 预防胜于治疗:规范化通过消除冗余和确保数据完整性来帮助防止出现数据异常问题
4 系列文章
- 【如此简单!数据库入门系列】之初识数据库 – 数据库基本概念
- 【如此简单!数据库入门系列】之学霸数据库 – 数据库基本原理
- 【如此简单!数据库入门系列】之学霸数据库(2) – 数据库基本原理
- 【如此简单!数据库入门系列】之学霸数据库(3) – 数据库基本原理
- 【如此简单!数据库入门系列】之前世今生 – 数据库历史
- 【如此简单!数据库入门系列】之前世今生(2) – 数据库历史
- 【如此简单!数据库入门系列】之ER模型快速入门
- 【如此简单!数据库入门系列】之关系模型简介
- 【如此简单!数据库入门系列】之数据库设计基础–函数依赖
如果喜欢这篇文章,请不要忘记关注、点赞和收藏哦!
您的鼓励将是我创作的最大动力!