数据库设计范式
数据库设计范式详解
简介
设计关系数据库时,遵从不同的规范要求,设计出合理的关系型数据库,这些不同的规范要求被称为不同的范式,各种范式呈递次规范,越高的范式数据库冗余越小。
满足最低要求的范式是第一范式(1NF)。在第一范式的基础上进一步满足更多规范要求的称为第二范式(2NF),其余范式以次类推。一般说来,数据库只需满足第三范式(3NF)就行了。
前言
数据库范式是数据库设计中必不可少的知识,没有对范式的理解,就无法设计出高效率、优雅的数据库,甚至设计出错误的数据库。
而想要理解并掌握范式却并不是那么容易。教科书中一般以关系代数的方法来解释数据库范式。这样做虽然能够十分准确的表达数据库范式,但比较抽象,不太直观,不便于理解,更难以记忆。
概念
1、什么是范式(NF)?
按照教材中的定义,范式是“符合某一种级别的关系模式的集合,表示一个关系内部各属性之间的联系的合理化程度”。
简单的理解为:一张数据表的表结构所符合的某种设计标准的级别。数据库范式也分为1NF,2NF,3NF,BCNF,4NF,5NF。一般在我们设计关系型数据库的时候,最多考虑到BCNF就够。
2、什么是关系型数据库,非关系型数据库?
关系型数据库
采用了关系模型来组织数据的,简单来说,就是二维表格模型,而一个关系型数据库就是由二维表及其之间的联系所组成的一个数据组织。
常见的关系型数据库:Oracle、DB2、PostgreSQL、Microsoft SQL Server、Microsoft Access、MySQL。
关系型数据库特点
容易理解:二维表结构是非常贴近逻辑世界的一个概念,关系模型相对网状、层次等其他模型来说更容易理解
使用方便:通用的SQL语言使得操作关系型数据库
易于维护:丰富的完整性(实体完整性、参照完整性和用户定义的完整性)大大减低了数据冗余和数据不一致的概率
非关系型数据库
指非关系型的,分布式的,且一般不保证遵循ACID原则的数据存储系统。
非关系型数据库以键值对存储,且结构不固定。
每一个元组可以有不一样的字段,每个元组可以根据需要增加一些自己的键值对。
不局限于固定的结构,可以减少一些时间和空间的开销。
非关系型数据库特点
提升性能:key-value数据库的主要特点是具有极高的并发读写性能。例如:Redis, Amazon DynamoDB, Memcached,Microsoft Azure Cosmos DB和Hazelcast
快读查询:在海量的数据中可以快速的查询数据。例如:MongoDB,Amazon DynamoDB,Couchbase,Microsoft Azure Cosmos DB和CouchDB
数据搜索:对海量数据进行近实时的处理和分析处理,可用于机器学习和数据挖掘。例如:例如:MongoDB,Amazon DynamoDB,Couchbase,Microsoft Azure Cosmos DB和CouchDB
3、理解一下概念
实体:现实世界中客观存在并可以区分的事物。(例如:一个人,老师与学校的关系,这里说的事物不仅仅是指看的见摸得着的东西,也可以是虚拟的东西。)
属性:实体具有的某一特性。(例如:性别是人的一个属性。在关系型数据库中属性又是一个物理概念,可以看做是表的一列)
元组:表中的每一条记录,也就是每一行。
分量:元组的某个属性。它是一个操作原子,关系型数据库在任何操作的时候,属性是不可分的,否则就不是关系型数据库了。
码:关系中的某个属性或者某几个属性的组合。如果这样的码有不止一个,那么大家都叫候选码,我们从候选码中挑一个出来做老大,它就叫主码。
全码:如果一个码中包含了所有的属性,这个码就是全码。
主属性:一个属性只要在任何一个候选码中出现,这个属性就是主属性。
非主属性:与上面相反,没有在任何候选码中出现过,这个属性就是非主属性。
外码:一个属性或者属性组,它不是码,但是别的表的码,它就是外码。
4、范式详解
第一范式(1NF)属性不可分原则
上面已经介绍了属性的概念,我们说它是不可分的。那么它和属性值不可分有什么区别的,下面看一个简单的例子。
例子:
姓名 | 电话 | 年龄 | |
小明 | 15864794485 | 28 | |
小红 | 13365468795 | 010-1234567 | 29 |
在上面的例子中,对于小红属性值的属性电话分成了两个,是可分的。
姓名 | 电话 | 年龄 | |
手机 | 座机 | ||
小明 | 15612345678 | 010-22222 | 28 |
小红 | 15812345678 | 010-11111 | 29 |
这两种情况都不能满足数据库设计的第一范式,属性不可分的原则。那么正确的做法应该是这样的。
姓名 | 年龄 | 手机 | 座机 |
小明 | 28 | 15612345678 | |
小红 | 29 | 15612345678 | 010-12345678 |
我们在这里把电话分成了 手机 和 座机 两个属性。
但是仅仅符合1NF的设计是远远不够的,仍然会存在数据冗余过大,插入异常,删除异常,修改异常的问题。例如表4
学号 | 姓名 | 系名 | 系主任 | 课程 | 分数 |
10001 | 李明 | 经济系 | 大宝 | 数学 | 98 |
10001 | 李明 | 经济系 | 大宝 | 英语 | 88 |
10002 | 王毅 | 法律系 | 付龙 | 数学 | 98 |
10002 | 王毅 | 法律系 | 付龙 | 语文 | 89 |
10003 | 李丹 | 英语系 | 李辰 | 体育 | 77 |
第二范式(2NF):在1NF的基础上,非码属性必须完全依赖于候选码(在1NF基础上消除非主属性对主码的部分函数依赖)
什么是函数依赖?
我们可以这么理解,若果在一张表中属性(或属性组)A 的值确定的情况下,必定能确定属性B的值,那么就可以说B依赖于A,写成 A->B 函数关系表达式 B=F(A);
简单理解为A的值确定的情况下,B的值一定时确定的。在数据表中不存在任意两条记录,A的属性(属性组)上的值相同,而在B的属性上不同。例如学号一样,姓名却不一样的两条记录。
也就是表4中的姓名依赖于学号,写作 姓名->学号 。而不是学号依赖于姓名(名字一样的人很多)。
函数依赖分为三种:
1、完全依赖
2、部分依赖
3、传递依赖
如果B依赖于A,记做 A->B,而且B不依赖于任何一个A的真子集,则B完全依赖于A,称为完全依赖。例如 姓名完全依赖于学号,记做:学号->姓名,(学号,课程)->分数,分数依赖于属性组,分数对应学号是不成立的,分数对应课程也不成立;
反之如果B能依赖于A的真子集,则称B部分函数依赖于A,称为部分依赖。例如:(学号,课程)->姓名,即学号确定了就能确定姓名,但是课程确定了不能确定姓名;
如果C依赖于B,且B又依赖于A,则称C传递函数依赖于A,称为传递依赖。
子集是一个数学概念:如果集合A的任意一个元素都是集合B的元素,那么集合A称为集合B的子集。
如果集合A是B的子集,且A≠B,即B中至少有一个元素不属于A,那么A就是B的真子集。如图
第二步:根据第一步所得到的码,找出所有的主属性。
第三步:数据表中,除去所有的主属性,剩下的就都是非主属性了。
第四步:查看是否存在非主属性对码的部分函数依赖。
我们得到,表4的码只有一个(学号,课名),主属性有两个学号、课名,非主属性有4个姓名、系名、系主任、分数
对于(学号,课名)-> 姓名,有 学号 -> 姓名,存在非主属性 姓名 对码(学号,课名)的部分函数依赖。
对于(学号,课名) -> 系名,有 学号 -> 系名,存在非主属性 系名 对码(学号,课名)的部分函数依赖。
对于(学号,课名) -> 系主任,有 学号 -> 系主任,存在非主属性 对码(学号,课名)的部分函数依赖。
对于表4分解后的表为:
学号 | 课名 | 分数 |
10001 | 数学 | 98 |
10001 | 英语 | 88 |
10002 | 数学 | 98 |
10002 | 语文 | 89 |
10003 | 体育 | 77 |
学号 | 姓名 | 系名 | 系主任 |
10001 | 李明 | 经济系 | 大宝 |
10002 | 王毅 | 法律系 | 付龙 |
10003 | 李丹 | 英语系 | 李辰 |
现在如果进行操作看是否存在问题。
1、李明转入法律系,只需要修改一下对应的系的值即可,有改进。
2、数据的冗余减少了,不像之前的重复信息太多,有改进。
3、删除某一个系中所有的学生记录,该系的一些信息会全部丢失,无改进。
4、插入一个新的系,无法插入,无改进。
所以,只单单满足2NF要求是远远不够的,仍然存在非主属性系主任对于码学号的传递函数依赖。
第三范式(3NF):3NF在2NF的基础之上,消除了非主属性对于码的传递函数依赖。
对于选课表,主码为(学号,课名),主属性为学号和课名,非主属性只有一个分数,不可能存在传递函数依赖,所以选课表的设计,符合3NF的要求。
为了让数据表设计达到3NF的要求,我们必须进一步进行模式分解为以下形式:
选课(学号,课名,分数)
学生(学号,姓名,系名)
系(系名,系主任)
对于选课表,符合3NF的要求,之前已经分析过了。
对于学生表,码为学号,主属性为学号,非主属性为系名,不可能存在非主属性对于码的传递函数依赖,所以符合3NF的要求。
对于系表,码为系名,主属性为系名,非主属性为系主任,不可能存在非主属性对于码的传递函数依赖(至少要有三个属性才可能存在传递函数依赖关系),所以符合3NF的要求。。
学号 | 课名 | 分数 |
10001 | 数学 | 98 |
10001 | 英语 | 88 |
10002 | 数学 | 98 |
10002 | 语文 | 89 |
10003 | 体育 | 77 |
学号 | 姓名 | 系名 |
10001 | 李明 | 经济系 |
10002 | 王毅 | 法律系 |
10003 | 李丹 | 英语系 |
系名 | 系主任 |
经济系 | 大宝 |
法律系 | 付龙 |
英语系 | 李辰 |
现在我们来看一下,进行同样的操作,是否还存在着之前的那些问题?
1、删除某个系中所有的学生记录该系的信息不会丢失。——有改进
2、插入一个尚无学生的新系的信息。因为系表与学生表目前是独立的两张表,所以不影响。——有改进
3、数据冗余更加少了。——有改进
结论
由此可见,符合3NF要求的数据库设计,基本上解决了数据冗余过大,插入异常,修改异常,删除异常的问题。
当然,在实际中往往为了性能上或者应对扩展的需要,经常可以做到2NF或者1NF,但是作为数据库设计人员,至少应该知道,3NF的要求是怎样的。
BCNF范式(巴斯-科德范式)在3NF基础上,任何非主属性不能对主键子集依赖(在3NF基础上消除对主码子集的依赖)
巴斯-科德范式(BCNF)是第三范式(3NF)的一个子集,即满足巴斯-科德范式(BCNF)必须满足第三范式(3NF)。
通常情况下,巴斯-科德范式被认为没有新的设计规范加入,只是对第二范式与第三范式中设计规范要求更强,因而被认为是修正第三范式,也就是说,它事实上是对第三范式的修正,使数据库冗余度更小。
这也是BCNF不被称为第四范式的原因。
BCNF的关系模式有:
1、所有非主属性对每一个码都是完全函数依赖。
2、所有主属性对每一个不包含它的码也是完全函数依赖。
3、没有任何属性完全函数依赖于非码的任何一组属性。
按定义排除了任何属性对码的传递依赖与部分依赖,一般关系型数据库设计中,达到BCNF就可以了!
举例说明:
1、某公司有若干个仓库
2、每个仓库只能有一名管理员,一名管理员只能在一个仓库中工作
3、一个仓库中可以存放多种物品,一种物品也可以存放在不同的仓库中。每种物品在每个仓库中都有对应的数量
已知函数依赖集:仓库名->管理员,管理员->仓库名,(仓库名,物品名)-> 数量
码:(管理员,物品名),(仓库名,物品名)
主属性:仓库名、管理员、物品名
非主属性:数量
不存在非主属性对码的部分函数依赖和传递函数依赖,那么此关系模式是属于3NF的。
然而,这个关系模式是否存在问题呢,下面我来具体分析一下。
1、增加一个仓库,但尚未存放任何物品,是否可以为该仓库指派管理员?——不可以,因为物品名也是主属性,根据实体完整性的要求,主属性不能为空。
2、某仓库被清空后,需要删除所有与这个仓库相关的物品存放记录,会带来什么问题?——仓库本身与管理员的信息也被随之删除了。
3、如果某仓库更换了管理员,会带来什么问题?——这个仓库有几条物品存放记录,就要修改多少次管理员信息。
所以,在某些特殊情况下,即使关系模式符合 3NF 的要求,仍然存在着插入异常,修改异常与删除异常的问题。需要再次改进设计。
造成此问题的原因:存在着主属性对于码的部分函数依赖与传递函数依赖。主属性仓库名对于码(管理员,物品名)的部分函数依赖。
解决办法就是要在 3NF 的基础上消除主属性对于码的部分与传递函数依赖即可。
仓库(仓库名,管理员)
库存(仓库名,物品名,数量)
这样,之前的插入异常,修改异常与删除异常的问题就被解决了。