本文主要是介绍深入了解数据库设计中的规范化与反规范化,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
零、前言
一、一些基本术语
二、关系模式
2.1. 什么是关系模式
2.2. 示例
三、数据依赖
3.1. 函数依赖
3.1.1. 完全函数依赖
3.1.2. 部分函数依赖
3.1.3. 传递函数依赖
3.2. 多值依赖
3.3. 连接依赖
四、规范化
4.1. 第一范式(1NF)
4.2. 第二范式(2NF)
4.3. 第三范式(3NF)
4.4. 巴斯-科德范式(BCNF)
4.5. 第四范式(4NF)
4.6. 第五范式(5NF)
4.7. 第六范式(6NF)
五、反规范化
5.1. 冗余列
5.2. 派生列
5.3. 表重组
5.4. 表分割
5.4.1. 水平分割
5.4.2. 垂直分割
零、前言
为了更好的理解数据库设计的规范化和反规范化,我们需要先弄清楚什么是函数依赖,以及函数依赖的分类,而要理解函数依赖,就要先明白什么是关系模式。所以,本文从关系模式开始,再依次讲解函数依赖和规范化以及反规范化。
一、一些基本术语
属性 (Attribute): 在现实世界中,要描述一个事物常常取若干特征来表示。这些特征称为属性。例如学生通过学号、姓名、性别、系别、年龄、籍贯等属性来描述。
域 (Domain): 每个属性的取值范围对应一个值的集合,称为该属性的域。例如,学号的域是6位整型数;姓名的域是10位字符;性别的域为{男,女}等。
目或度 (Degree): 目或度指的是一个关系中属性的个数。
候选码 (Candidate Key): 若关系中的某一属性或属性组的值能唯一的标识一个元组,则称该属性或属性组为候选码。
主码 (Primary Key): 或称主键,若一个关系有多个候选码,则选定其中一个作为主码。
主属性 (Prime Attribute): 包含在任何候选码中的属性称为主属性。不包含在任何候选码中的属性称为非主属性 (Non-Prime Attribute)。
外码 (Foreign Key): 如果关系模式 R 中的属性或属性组不是该关系的码,但它是其他关系的码,那么该属性集对关系模式R 而言是外码。例如,客户与贷款之间的借贷联系 c-1(c-id,loan-no), 属性c-id是客户关系中的码,所以c-id 是外码;属性 loan-no是贷款关系中的码,所以loan-no 也是外码。
全码 (All-key): 关系模型的所有属性组是这个关系模式的候选码,称为全码。例如,关系模式R(T,C,S), 属性 T 表示教师,属性C 表示课程,属性S 表示学生。假设一个教师可以讲授多门课程,某门课程可以由多个教师讲授,学生可以听不同教师讲授的不同课程,那么,要想区分关系中的每一个元组,这个关系模式R 的码应为全属性 T、C和 S,即ALL-KEY。
二、关系模式
2.1. 什么是关系模式
数据库的关系模式是关系型数据库中用于描述数据库结构和数据之间关系的一种方式。它是对数据库中关系(表)的逻辑结构进行定义,可以形式化地表示为:R(U,D,dom,F),其中每个元素都有特定的语义,使用这样的表示方法可以帮助数据库设计者规范地描述数据库中的数据结构,以及数据之间的逻辑关系。这有助于确保数据的完整性和准确性,并为数据库的查询和操作提供了一个清晰的基础。
通常,我们可以将关系模式简记为R(U)。
- R:表示关系的名称,代表数据库中的一个表或关系。
- U:表示关系R的属性集合。这些属性定义了关系中的列。
- D:表示属性集合U中每个属性的域或数据类型集合。域是对属性可能值的限制,确保每个属性的值都属于其指定的数据类型。
- dom:表示属性集合U中每个属性的值域,是属性到域的映像集合;
- F:表示关系R的函数依赖集合。
2.2. 示例
假设我们有一个关系模式叫做“学生成绩”,它用于存储学生的信息和他们的成绩。我们可以用R(U,D,dom,F)来描述这个关系模式:
- R:“学生成绩”
- U:{学号, 姓名, 课程, 成绩}
- D:{整数, 字符串, 字符串, 浮点数}
- dom:
成绩:分数,例如“90.5”
课程:课程名称,例如“数学”
姓名:学生的名字,例如“张三”
学号:唯一的 student ID,例如“S12345”
F:{学号→姓名, (学号,课程)→成绩}
在这个例子中,学号决定了学生的姓名和课程,因为每个学生只有一个学号,而且每个课程的成绩也是由特定的学号和课程共同决定的。这样的函数依赖集合确保了数据的完整性和一致性。例如,如果我们有一个学生的学号是“S12345”,那么我们可以通过学号查找到对应学生的姓名。同时,如果我们知道一个学生选修了“数学”课程,我们可以查找该课程的成绩。
以上关系模式,可以简记为:学生成绩(学号, 姓名, 课程, 成绩)
三、数据依赖
3.1. 函数依赖
在上述关系模式中的说明中,我们提到了函数依赖(F),数据库中的函数依赖是描述关系型数据库中表的属性之间相互依赖关系的概念。它反映了表中数据属性之间的逻辑关系,即一个属性或属性集是否能够决定另一个属性或属性集的值。
在关系型数据库中,一个关系(表)由多个属性(列)组成。函数依赖定义了这些属性之间的规则,说明了一个属性的值如何决定另一个属性的值。函数依赖有助于确保数据的准确性和一致性。
通俗来讲,函数依赖是使关系模式的属性间的数据依赖达到一种“函数”的效果,即根据一个或多个属性,能够推导出唯一的另一个属性,它描述的实际上是属性之间1对1或n对1的关系。
函数依赖主要有三种类型:完全函数依赖、部分函数依赖以及传递函数依赖。
3.1.1. 完全函数依赖
当一个属性集合A完全决定另一个属性集合B时,我们说B完全函数依赖于A。这意味着A中的每个值都决定了B中的一个唯一值。例如,(学号,课程号) → 成绩,这里学号和课程号单独拿出来都不能决定成绩,只有学号和课程号一起才能决定一个成绩。
3.1.2. 部分函数依赖
当一个属性集合A的部分成员决定了另一个属性集合B时,我们说B部分函数依赖于A。这意味着A中的某些值可能决定多个B的值。例如,(学号,课程号) → 学生姓名,这里学号和课程号放在一起可以决定学生的姓名,但是它不满足完全函数依赖,因为学号单独拿出来也能决定学生姓名。
3.1.3. 传递函数依赖
当一个属性集合A通过中间属性集合B决定另一个属性集合C时,我们说C传递函数依赖于A。这意味着A中的每个值通过B决定了C中的一个唯一值。例如,学号 → 所在班级,所在班级 → 班主任,这里班主任依赖于所在班级,而所在班级又依赖于学号。
3.2. 多值依赖
多值依赖是函数依赖的一种扩展,它用于描述一个属性集合对另一个属性集合的多值关系。在关系模式R(A, B, C)中,如果对于R中的任意两个元组t1和t2,当t1在属性A上的值等于t2在属性A上的值时,t1在属性B上的值集合必须等于t2在属性B上的值集合,那么我们说B多值依赖于A,记作A →→ B。多值依赖表达了属性间的一种更为复杂的关系。
与函数依赖描述1对1或n对1的关系不同,多值依赖主要描述属性之间1对n或m对n的关系。如下图,在图中的关系模式中,其属性关系为:科目与学生之间、科目与教师之间均存在多对多的关系,但学生和教师之间没有任何直接关系:
将这些对应关系拆分则得到了如下两个子关系:
此时,我们称学生、科目和教师这三个属性之间存在多值依赖。
3.3. 连接依赖
根据《计算机科学技术名词 》第三版的定义,如果将关系 R的所有属性在R的实例I上分别进行投影操作,然后将这些投影的结果进行连接运算后其结果仍等于I,则称该实例I 满足连接依赖。
以上定义或许不太好理解,这里还是以上面提到的关系为例,但在这个关系模式中,其语义约束变为:学生与科目之间、学生与教师之间、科目与教师之间均存在多对多的对应条件:
将这些对应关系拆分则得到了如下三个子关系:
此时重新将R1、R2和R3三张子表进行自然连接运算之后,可以无损地还原出原始关系R,则我们称学生、科目和教师这三个属性之间存在连接依赖。
到这里便可以发现,连接依赖和多指依赖都是由多对多或多对一的联系而产生的,如果三个属性之间任意两个属性均存在多对多或多对一的关系,则称这三个属性之间存在连接依赖, 否则称这三个属性之间存在多值依赖。
四、规范化
数据库设计规范化(Database Normalization)是数据库设计中的一种重要方法,旨在通过优化数据库结构来提高数据的一致性和完整性,减少数据冗余,增强数据修改的灵活性,并降低数据查询的复杂性。规范化通常涉及将大表拆分成更小、更专门的表,并通过定义表之间的关系来连接这些表。通常可以通过判断分解后的模式达到几范式来评价模式规范化的程度。范式从低到高依次为:1NF、2NF、3NF、BCNF、4NF以及5NF。在实际应用中,3NF已经足够满足大多数数据库设计的需求。下面依次对这些范式进行说明:
4.1. 第一范式(1NF)
若存在学生表如下:
在此学生表中,Courses包含了学生所选的所有课程,这种表设计使得查询某课程被哪些学生选择变得复杂,并且在新增选课、取消选课时也需要先分解Courses字段才能进行处理。为了解决这种问题,提出了1NF。
1NF的目标是确保表中的每一列都是不可分割的基本数据项,主要关注的是确保每列的原子性,即没有集合、数组、记录等复杂数据类型。例如,供应者和它所提供的零件信息,关系模式 FIRST和函数依赖集F 如下:
FIRST(Sno,Sname,Status,City,Pno,Qty)
F={Sno → Sname,Sno → Status,Status → City,(Sno,Pno)→ Qty}
对具体的关系 FIRST 如图所示,可以看出,每一个分量都是不可再分的数据项,所以是1NF的。
4.2. 第二范式(2NF)
在以上关系模式中,我们可以看到它的候选码为(Sno,Pno),存在部分函数依赖(Sno可以单独推断出Sname和Status),这就导致其存在插入异常和删除异常的问题,所谓插入异常,是指按照关系模式实体完整性规定主码不能取空值或部分取空值(由于候选码只有一个,所以(Sno,Pno)也是主码)。这样,当某个供应者的某些信息未提供时(如 Pno), 则不能进行插入;而删除异常是指若供应商S4的P2零件销售完了,并且以后不再销售P2零件,那么应删除该元组,可S4 又是客观存在的,不能因此删除。而为了解决插入异常和删除异常,就有了2NF。
2NF的目的是消除部分函数依赖,要求非主键列必须完全依赖于整个主键,而不能仅依赖于主键的一部分。比如,为了消除以上示例中的部分函数依赖,我们可以将FIRST关系分解为:
FIRST1(Sno,Sname,Status,City)
FIRST2(Sno,Pno,Qty)
因为分解后的关系模式FIRST1的码为 Sno, 非主属性 Sname、Status、City完全依赖于码Sno, 所以属于2NF; 关系模式FIRST2的码为 (Sno,Pno), 非主属性Qty完全依赖于码,所以也属于2NF。
4.3. 第三范式(3NF)
在满足了2NF之后,继续分析可以发现,由于关系FIRST1中存在传递函数依赖(Sno → Status,Status → City),导致其存在修改操作的不一致性的问题,修改操作的不一致性是指:如Status=20对应的City从天津搬到了上海,则有可能导致一些Status=20的数据被修改,而另一些未被修改,导致数据产生不一致性,为了解决这种问题,就需要3NF
3NF的目的是消除传递函数依赖,也就是说,非主键列必须直接依赖于主键,而不能依赖于其他非主键列。为了使前面的关系达到3NF,我们将FIRST1进一步分解为:
FIRST11(Sno,Sname,Status)
FIRST12(Status,City)
通过上述分解,数据库模式FIRST转换为FIRST11(Sno,Sname,Status), FIRST12(Status,City),FIRST2(Sno,Pno,Qty)3个子模式。由于这3个子模式都达到了3NF, 因此分解后的数据库模式达到了3NF。
4.4. 巴斯-科德范式(BCNF)
BCNF是3NF的一种改进形式,它有三点要求:
所有非主属性对每一个码都是完全函数依赖
所有的主属性对每一个不包含它的码,也是完全函数依赖
没有任何属性完全函数依赖于非码的任何一组属性
简单来讲,就是所有关系的左侧都必须能够定位到唯一的一条数据。例如:存在关系模式如下:
R(a,b,c,d)
F((a,b)→c,(a,c)→d)
在以上关系模式中,候选键为(a,b),由于其中不存在非主键列对主键的部分依赖,同样也不存在非逐渐列对主键的传递依赖,所以它是满足3NF的,但d对候选码(a,b)不为完全函数依赖,所以违反了上述3点中的第一点,即不满足BCNF。为了使之满足BCNF,我们可以对关系进行拆分:
R1(a,b,c)
R2(a,c,d)
4.5. 第四范式(4NF)
假设存在如下表:
在该表中,候选键为(学生编号,选课,爱好),即全部属性都是主属性,且其不存在函数依赖,所以它是满足BCNF的,但是由于学生编号和选课之间、学生编号和爱好之间分别存在多值依赖,且选课和爱好之间没有任何关系,所以这里存在数据冗余,每多一个选课都要添加两条记录(对应篮球和羽毛球)。4NF的目的就是为了解决这种冗余,即消除关系中的多对多关系,将之拆分成多个1对多关系,例如上述表为了满足4NF,我们可以将之拆分为如下两张表:
4.6. 第五范式(5NF)
在上一小节的例子中,如果补充一点约束:只有爱好为篮球的学生才可以选数学和语文,只有爱好为羽毛球的学生才可以选英语和化学,那么此时由于学生编号、爱好和选课三个属性之间任意两个属性之间都存在多值约束,此时这三个属性之间就变成了连接依赖,不存在多值依赖,也就自然满足了4NF,而在4NF的基础上,进一步消除其不由候选码隐含的连接依赖,就得到了5NF。所谓“消除其不由候选码隐含的连接依赖”,就是要保证不存在任何依赖属性不为候选码的连接依赖。例如上面所述关系中,其候选码为(学生编号,选课,爱好),而在其连接依赖中,学生编号→选课和学生编号→爱好之间的连接属性为学生编号,爱好→选课和学生编号→爱好之间的连接属性为爱好,爱好→选课和学生编号→选课之间的连接属性为选课,可见它们的连接属性均不为候选码,所以它不满足5NF。
为了使上述关系满足5NF,我们需要针对对其连接依赖进行拆分,将之拆分为R1(学生编号,选课),R2(学生编号,爱好)和R3(选课,爱好)。
4.7. 第六范式(6NF)
6NF是为了处理时间数据独立变化的情况而创建的,以避免不必要的重复。其结果是表无法进一步分解;在大多数情况下,表包括主键和单个非键属性。如下图:
在该关系表中,部门和级别往往是随着时间独立变化的,这就导致该表中可能存在大量的重复数据(比如级别不变但部门一直在调整),为了避免这种冗余数据,我们可以将表拆分为如下两张表,以满足6NF:
通过将“时变数据”隔离到自己的表中,与时限属性相关的操作变得更加高效和清晰。此结构使处理和查询时态数据变得更加容易。在实践中,6NF一般仅应用于需要复杂时态数据管理的系统。此外,虽然 6NF 有助于处理时态数据,但它可能会以多个表的形式引入复杂性,这可能需要在查询期间进行复杂的连接。
五、反规范化
在关系模式的规范化过程中,会导致关系的概念愈来愈单一化,在响应用户查询时,往往需要涉及多表的关联操作,导致查询性能下降。为此需要对关系模式进行修正,对部分影响性能的关系模式进行处理,包括分解、合并、增加冗余属性等。这种修正称之为反规范化设计,反规范化通过有意地违反数据库设计中的规范化原则,将数据冗余存储在多个表中,通过减少表之间的连接来提高查询性能,从而加快查询速度。但是,反规范化也会增加数据冗余,增加数据更新和维护的难度。因此,在进行规范化时,需要仔细考虑权衡各种因素,以确保数据的一致性完整性。
反规范化数据库不应该与从未进行过标准化的数据库相混淆。常见的反规范化操作有冗余列、派生列、表重组和表分割,其中表分割又分为水平分割和垂直分割。反规范化会在数据库中形成数据冗余,为解决数据冗余带来的数据不一致性问题,设计人员往往需要额外采用数据同步的方法来解决这种数据不一致性。常见的方法有应用程序同步、批量处理同步和触发器同步等。
5.1. 冗余列
冗余列是指在一个表中重复存储相同的信息,以便于快速访问和提高查询效率。例如,如果一个订单表中包含了客户ID和地址ID,而这些信息又分别存储在另一个客户表和地址表中,那么可以在订单表中直接存储这些冗余信息,从而避免了多次表连接的需要,提高查询速度。
5.2. 派生列
派生列是基于其他列计算得出的列。在反规范化设计中,可以通过添加派生列来存储计算结果,这样可以减少对原始数据的查询次数。例如,如果一个销售表中需要频繁计算总金额,可以在该表中添加一个派生列来存储这个总金额,从而提高查询效率。
5.3. 表重组
表重组是指重新组织数据库中的表结构,使其更适合于特定的查询需求。这种方法通常涉及到将多个小表合并成一个大表,或者将一个大表拆分成多个小表。这样做的目的是减少表之间的关系,简化查询逻辑,从而提高查询性能。
5.4. 表分割
表分割是指将一个大表根据某些条件分割成多个小表。每个小表只包含部分数据,但这些小表共同构成了原来的大表。这种方法可以有效地利用分布式数据库系统的特性,通过将数据分布到不同的服务器上,可以显著提高查询性能和数据处理能力。
5.4.1. 水平分割
水平分割是根据数据行的特点进行分割,分割之后所得的所有表的结构都相同,而存储的数据不同。例如我国的身份证号码若放在一个表中,由于我国人口众多,因此数据量大,进行身份证查询时效率低。这时可以按省份对它进行水平分割,把不同区域的身份证号分别存储在不同的表中,查询时只需根据省份代码检索相应的表,这显然能提高查询速度。水平分割会给应用增加复杂度,特别在查询所有数据需要union(并)操作。在许多数据库应用中,这种复杂性会超过它带来的优点,因此,只有当表中数据具有很好独立性时才适合使用此方法。
5.4.2. 垂直分割
垂直分割是根据数据列的特点进行分割,分割之后所得的所有表中除了都含有主码列外其余列都不相同。一般在一个表中某些列常用,而另外一些列不常用的情况下可以采用垂直分割,分割时把操作时常用与不常用的列分别放入不同的表中,这就使得数据行变小,一个数据页就能存放更多的数据,在查询时就会减少I/O次数,而且大多数据操作只在少数或者一个表中进行,从而提高系统性能,但其缺点是查询所有数据时需要join(链接)操作。
这篇关于深入了解数据库设计中的规范化与反规范化的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!