likes
comments
collection
share

数据库设计的基础——数据库范式

作者站长头像
站长
· 阅读数 7

前言

通常,我们设计的数据库大部分都是符合第三范式或者 BC 范式的,但是,本着知其然,还要知其所以然的精神,我们还是要了解这些范式解决了什么问题,还未解决什么问题,这些问题带来的影响是什么。

什么是范式?

数据库范式,又称为数据库规范化,用于保证数据库之间的关系合理,减少数据库中的数据冗余,消除操作带来的异常,增进数据的一致性。

下面是维基百科中的解释:

Database normalization is the process of structuring a relational database in accordance with a series of so-called normal forms in order to reduce data redundancy and improve data integrity. It was first proposed by Edgar F. Codd as part of his relational model.

用一句话来概括就是:数据库设计范式可以帮助我们减少数据的冗余,同时提高数据的完整性。

前置知识

在学习数据库范式之前,我们必须了解一些前置知识,这些知识在后续的内容将会大量使用,如果对这些不了解,那么将会对后面的内容一头雾水。

首先,我们定义一张表,并添加一些数据,这个表 1 (选课表)有助于我们理解这些概念:

数据库设计的基础——数据库范式

关系中的某个或者某几个属性的集合,用于唯一地标识每一条数据(这里的每一条数据就是数据库中的每一条记录)。

候选码

在一个表的关系中,可以存在多个关系集合用于唯一确定一条记录,这些多个集合就称为候选码,也称为候选键。候选码可以存在多个,每一个候选码都可以唯一地确定一条记录。

我们换一种更加严谨的说法:假设 K 为某个表中的一个属性或者属性组,如果除去 K 之外的所有属性都完全函数依赖(稍后会介绍)于 K,那么我们就称 K 为候选码。

根据上表的示例我们可以得出一个候选码:

  • (学号,课名)

主码

通常我们会从候选码中选择一个码作为主码,也就是我们通常所说的主键。

函数依赖

在数学上的解释是:数据库设计的基础——数据库范式,输入一个 X,可以得到一个确定的 Y。有点类似于纯函数。

对应到一个表上就是,在一个属性(属性组)X 确定的情况下,必定能够确定属性 Y 的值,这就能够称作 Y 函数依赖于 X,写作 X -> Y​ 。

记作:

数据库设计的基础——数据库范式

比如下面这些关系都存在函数依赖:

  • (学号)->(姓名)
  • (学号,课名)->(分数)
  • (系名)->(系主任)

但是,下面这些关系就不存在函数依赖:

  • (姓名)->(学号),因为有可能会出现重名的情况,所以只依靠姓名是无法确定学号的。
  • (学号)->(分数),因为一个学号有多个科目,每一个科目都存在一个分数,不能只靠学号来确定分数。
  • ……

完全函数依赖

在一个表中,如果存在 X -> Y,那么对于 X 下的任何一个真子集(X’),X’ -> Y都不成立,那么我们就说 Y 对于 X 完成函数依赖。

记作:X ->F Y

数据库设计的基础——数据库范式

通俗的说,必须通过码中的所有属性才可以唯一确定一个值。比如:

  • (学号)->F(姓名)
  • (学号,课名)->F(分数)

部分函数依赖

在一个表中,如果存在 X -> Y,但是 Y 并不完成依赖于 X。存在一些 X 的子集 X’,X‘ -> Y成立,那么我们就说 Y 对于X 部分函数依赖。

记作:X ->P Y

数据库设计的基础——数据库范式

通俗的说,只需要码中的部分属性即可唯一确定一个值。比如:

  • (学号,课名)-> (姓名),只需要根据码中的学号即可唯一确定姓名。

它跟完全函数依赖的区别在于,完全函数依赖必须要通过码中的所有属性才可以唯一确定一个值,而部分函数依赖只需要码中的部分属性即可。

传递函数依赖

如果 Z 函数依赖于 Y,Y 函数依赖于 X,并且 X 不函数依赖于 Y,那么我们就说 Z 传递函数依赖于 X。

记作:X ->T Z

数据库设计的基础——数据库范式

通俗的说,通过码可以唯一确定一个属性,然后通过该属性可以唯一确定另一个属性,所以就演变为了可以通过码唯一确定一个无函数依赖的属性。

属性

属性就是我们在表中定义的每一个列。

主属性

在码中的所有属性(每一列)都称为主属性

非主属性

除了主属性之外的其他属性,都称为非主属性。

范式

有了上面的那些概念,我们现在就可以正式来谈谈范式了。数据库设计范式有很多种:1NF、2NF、3NF、BCNF、4NF、5NF、6NF 等等。

但是并不是说遵循的范式等级越高越好,范式过高虽然具有对数据关系有更好的约束性,但是也会导致表之间的关系更加繁琐,从而导致每次操作的表会变多,数据库性能下降。

通常,在我的设计中,最高也就遵循到 BCNF,普遍还是 3NF。

1NF

1NF,又称第一范式。只要每一列中的数据都不可再分即可满足该范式,关系型数据库建立的表默认都支持该范式。但是一些开发人员会反其道而行,比如下面这种表 2 设计:

数据库设计的基础——数据库范式

这种设计是违反了第一范式的设计,因为这里的地址和亲属是可以继续划分的,而第一范式强调的就是属性不可再分。

结论:属性不可再分

2NF

1NF 只是设计数据库最基本的要求,但是数据会存在大量的冗余,并存在删除异常、插入异常、更新异常。我们把目光看到我们一开始的表 1 中。

在该表中,存在下面几个问题:

  • 每一名学生的学号、姓名、系名、系主任都是重复的数据,这都是冗余数据。
  • 假如学校新建了一个系,但是这个系还未招收学生。那么就无法将系名与系主任关联起来,这就是插入异常。
  • 假如系里的学生都毕业了,并且还未招收其他学生,那么该表中的学生删除之后,也同时将系的信息也删除了,这就会表现为不存在这个系,这就是删除异常。
  • 假如电子系要更换系主任了,那么就要将这个表里每一条有关电子系的系主任都进行更新,这就是更新异常。

为了解决上述的问题,我们就要引入更高一级的第二范式(2NF)。

2NF 对 1NF 做了哪些改进呢?

2NF 在 1NF 的基础上,消除了非主属性对码的部分函数依赖 。我们已经知道是可以唯一确定一条记录的属性集合,非主属性是除了码中属性之外的其他属性。

我们来分析表 1:

码:(学号、课名)

主属性:(学号、课名)

非主属性:(姓名、系名、系主任、班级、课名、分数)

我们回过头来分析一下表 1 是否符合 2NF,要符合 2NF 那么就不能存在非主属性对码的部分函数依赖。判断是否符合 2NF 可以通过以下步骤:

  1. 找出表中所有的码(候选码)
  2. 根据第一步得出的码找出所有的主属性
  3. 除了主属性之外的其他属性,就都是非主属性
  4. 判断是否存在非主属性部分函数依赖于码。

我们根据上面的步骤来验证表 1 是否符合 2NF。

第一步

找出表中所有的候选码。

如何找候选码呢?只有使用排列组合来寻找了。

  • 首先查看每一个属性,如果该属性确定,是否可以唯一地确定一条记录。
  • 查看每两个属性组合的属性组,如果该属性组确定,是否可以唯一地确定一条记录。
  • ……

以此类推,一直到所有属性组成的属性组为止。这看起来相当的繁琐,但是我们一般都可以通过直接观察得到,或者我们设计的时候就可以确定我们拥有的候选码有哪些。并不需要一个个的的自己组合判断。

如果一定要自己组合判断,我们可以根据候选码具有完全函数依赖的特性,可以推测出:如果 A 是码,那么任何与 A 组合的属性组都不符合码的要求,比如:(A,B)(A,B,C)等等。

我们根据上面的解释,我们在这一步中可以得出表 1 中的候选码:(学号、课名)

第二步

根据码找出所有的主属性。

这一步相对简单,第一步中找到的码只有(学号、课名)。所以主属性只有(学号、课名)。

第三步

除去主属性之外的其他属性,都是非主属性。

所以非主属性有(姓名、系名、系主任、班级、分数)。

第四步

判断第三步找出的非主属性是否部分函数依赖于第一步找出的码。

数据库设计的基础——数据库范式

我们根据上面的这张图,可以看出,有多个非主属性部分函数依赖于码。

  • 对于(学号,课名)->(姓名),存在(学号)->(姓名),存在非主属性姓名部分函数依赖于码。

  • 对于(学号,课名)->(系名),存在(学号)->(系名),存在非主属性系名部分函数依赖于码。

  • ……

所以对于表 1 来说,它不符合 2NF 的要求,它只能达到 1NF 的要求。

在这种情况下,我们应该如何改进才能让表 1 符合 2NF 的要求呢?当然是将表进行拆分,消除这些部分函数依赖,有个比较正式的名称:模式分解

我们可以将选课表进行拆分,将它拆分为两张表:选课表、学生表。

数据库设计的基础——数据库范式

数据库设计的基础——数据库范式

我们现在再来看选课表。此时它只有一个码:(学号,课名),非主属性只有一个:分数。它们之间已经不是部分函数依赖的关系了,而是完全函数依赖的关系。选课表中已经不存在部分函数依赖,所以此时选课表符合第二范式(2NF)。

我们现在来看看学生表是否符合 2NF。

按照上面的四个步骤:

  1. 码:(学号)
  2. 主属性:学号
  3. 非主属性:(姓名,系名,系主任,班级)
  4. 因为码中只有一个属性,所以不可能存在部分函数依赖。

所以学生表也是符合 2NF 的。

结论:在 1NF 的基础上,通过对表的拆分,消除表中非主属性对码的部分函数依赖

3NF

我们通过 2NF 已经将表 1 拆分成了两个表:选课表、学生表。但是依然存在着一些问题,比如:

  • 当删除学生表中的学生之后,会将系的信息也一起删除。如果将学生表的内容全部都删除,那么系信息也全部都不存在了。
  • 当创建一个新的系,但是还没有该系的学生,也无法添加该系的信息。

我们通过上一节已经知道选课表和学生表都是符合 2NF 的,但是学生表中依然存在着一些问题。所以,符合 2NF 的表不一定就能满足我们的要求。此时,我们就要用到 3NF。

只要表中存在非主属性对码的传递函数依赖,则不满足 3NF 的要求。所以对于 3NF 来说,就是在 2NF 的基础上,消除非主属性对码的传递函数依赖

我们来分析以下学生表中的函数依赖关系:

  1. 找出学生表中的码:(学号)
  2. 主属性:学号
  3. 非主属性:(学号,系名,系主任,分数)
  4. 学号与系主任之间存在传递函数依赖

数据库设计的基础——数据库范式

通过图可以看到,只要我们确定了学号,我们就可以确定系名,确定了系名,我们就可以确定系主任。因此在学号确定的情况下,我们也可以确定系主任。这就是传递函数依赖。

消除传递函数依赖的方法也是通过拆分表来完成,我们将学生表拆分为两张表:学生表、系表

数据库设计的基础——数据库范式

数据库设计的基础——数据库范式

在拆分之后,学生表就消除了函数传递依赖,所以此时学生表符合 3NF,系表也符合 3NF。

结论:在 2NF 的基础上,消除表中非主属性对码的传递函数依赖。

我们为了使表 1 符合设计范式,将表 1 拆分成了三个表。从而解决了数据的冗余、删除异常、添加异常、更新异常的问题。

但是这也带来了一个新的问题,我们查询数据的时候不能只通过查询一张表来得出结果,而要通过连接三张表一起查询才行,这也会影响数据库的查询性能。

BCNF

我们已经了解 1NF、2NF、3NF,也通过这三个范式解决了问题,那么为什么还会有 BCNF 呢?这个范式又解决了什么问题?我们带着这两个问题一起来看看 BCNF。

首先,要说明的是,上面经过拆分之后的三张表都是符合 BCNF的。看到这里,小朋友,你是否有很多问号?

别急,让我们先看一下 BCNF 的定义:不存在主属性对于码的部分函数依赖和传递函数依赖,那么即符合 BCNF。

注意:这里说的是主属性,而前面的 2NF、3NF都是非主属性对码的部分函数依赖和传递函数依赖。要注意这个区别,很重要。

我们可以看到上面三个表因为都只有一个码,所以不存在主属性对码的部分函数依赖和传递函数依赖。不符合BCNF 需要存在两个码以上。

比如下面这种情况:

  • 某公司有若干个仓库
  • 每个仓库只能有一名管理员,一名管理员只能在一个仓库中工作
  • 一个仓库中可以存放多种物品,一个物品也可以存放在不同的仓库。每种物品在每个仓库中都有对应的数量。

数据库设计的基础——数据库范式

我们来分析一下这个表:

  • 码:(仓库名,物品名),(管理员,物品名)
  • 主属性:仓库名、物品名、管理员
  • 非主属性:数量

非主属性只有“数量”,而非主属性对于两个码都不存在部分函数依赖和传递函数依赖,所以这个表是符合 3NF的。但是这个表却是不符合 BCNF 的。

因为存在主属性对码的部分函数依赖:

  • (仓库名,物品名)-> 管理员,只要确定仓库名,即可确定管理员,所以管理员部分函数依赖于仓库名。
  • (管理员,物品名)-> 仓库名,同上。

那么我们要将该表进行拆分才能让物品表符合 BCNF。将该表拆分为两个表:仓库表、物品表。

数据库设计的基础——数据库范式

数据库设计的基础——数据库范式

我们可以来看看拆分之后解决了什么问题:

  • 如果仓库换管理员,不需要将物品表中的每一个该仓库的数据都进行修改。
  • 如果物品都被删除,仓库依然存在,仓库管理员也依然于仓库存在关系
  • 如果新增一个仓库,但是还没有物品,可以直接在仓库表添加一条记录即可,数据库不会出错。

所以消除了主属性对码的部分函数依赖和传递函数依赖之后,数据库中的操作的异常就不再出现了。

结论:在 3NF 的基础上,消除主属性对码的部分函数依赖和传递函数依赖。

总结

同上上面的分析可见,我们在平时设计表的时候,虽然不知道这些范式,但是大部分都是有遵循这些范式的,至少也会遵循 2NF。因为只要我们对每个独立的个体建立表,大部分都能符合 3NF。

但是,我们不能因此而放弃理论知识,因为理论知识才是支撑你设计出更好的数据库结构的基础。

本文大量参考了如何理解关系型数据库的常见设计范式?中的表述。如果需要对数据库范式有更深入的理解,可以查阅该网页。

参考

  1. 如何理解关系型数据库的常见设计范式?
  2. 数据库规范化
  3. Database normalization