1.范式化设计
1.1.什么是范式
范式来自英文Normal Form,简称NF。
实际上你可以把它粗略地理解为 一张数据表的表结构所符合的某种设计标准的级别 。就像家里装修买建材,最环保的是E0级,其次是E1级,还有E2级等等
目前关系数据库有六种范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式5NF,又称完美范式)。
满足最低要求的范式是第一范式(1NF),在第一范式的基础上进一步满足更多规范要求的称为第二范式(2NF),其余范式以次类推。一般来说,数据库只需满足第三范式(3NF)或BCNF就行了
范式的定义会使用到主键和候选键,数据库中的键(Key)由一个或者多个属性组成。数据表中称用的几种键和属性的定义如下:
-
超键: 在关系中能唯一标识元组的属性集称为关系模式的超键
-
候选键: 不含有多余属性的超键称为候选键。也就是在候选键中,若再删除属性,就不是键了!
-
主键: 用户选作元组标识的一个候选键程序主键
-
外键:如果关系模式R中属性K是其它模式的主键,那么k在模式R中称为外键。
-
主属性: 包含在任意候选键中的属性为主属性
-
非主属性: 与主属性相对,指的是不包含在任何一个候选键中的属性
通常来说,我们也将候选键称之为 ”码“,把主键也称为 “主码”。因为键可能是由多个属性组成的,针对单个属性,我们还可以用主属性和非主属性来区分。
1.2.第一范式(1NF)
定义: 属于第一范式关系的所有属性都不可再分,即数据项不可分。
理解:第一范式强调数据表的原子性,是其他范式的基础。一张表有一个name-age列,这个列具有两个属性,一个name,一个 age,所以不符合第一范式,我们把它拆分成两列name和age,这张表就符合第一范式关系。
你在关系型数据库管理系统(RDBMS),例如SQL Server,MySQL中创建数据表的时候,1NF是所有关系型数据库设计的最基本要求。
第一范式详细的要求如下:
1、每一列属性都是不可再分的属性值,确保每一列的原子性;
2、两列的属性相近或相似或一样,尽量合并属性一样的列,确保不产生冗余数据;
3、单一属性的列为基本数据类型构成;
4、设计出来的表都是简单的二维表。
1.3.第二范式(2NF)
第二范式(2NF)是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)。
第二范式(2NF)要求实体的属性完全依赖于主关键字。
第二范式(2NF)是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)。
第二范式(2NF)要求实体的属性完全依赖于主关键字。
以上这张表不符合第二范式(2NF),虽然有主键,但是实体的属性不完全依赖于主关键字。
所谓完全依赖是指不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。
设计成两张表,主键分别是id和op_id,这样就符合第二范式(2NF)。
1.4. 第三范式(3NF)
满足第三范式(3NF)必须先满足第二范式(2NF);
第三范式(3NF)要求一个数据库表中不包含已在其它表中包含的非主关键字信息,即数据不能存在传递关系,即每个属性都跟主键有直接关系而不是间接关系。
产品表
这里如果产品ID或产品名称变化会发生什么情况?所以以上不符合第三范式(3NF)
以上订单表就符合第三范式
1.5.巴斯-科德范式(BCNF)
满足巴斯-科德范式(BCNF)必须先满足第三范式(3NF) BCNF被认为没有新的设计规范引入,只是对第三范式中设计规范要求更强,使得数据库冗余度更小。所以称为修正的第三范式,或扩充的第三范式,而不称为第四范式。 概念: 在关系R中,U为主键,A是主键的一个属性,若存在A->Y,Y为主属性,则该关系不满足BCNF
案例:
在这个表中,一个仓库只有一个管理员,一个管理员也只负责一个仓库。
依赖关系:仓库名称决定了管理员,管理员也决定了仓库名称,属性集合(仓库名称,管理员)决定了库存数量。
- 候选键:(仓库名称,物品名称)或者 (管理员,物品名称)
- 主属性:包含在任一候选键中的属性,也就是 仓库名称 管理员 物品名称
- 非主属性:库存数量
上表符合第三范式的要求,但是在发生数据增删改的过程中,出现下列几种情况,就会出现问题:
- 仓库更换管理员,有可能需要修改数据表中的 多条记录。
- 仓库商品全部下架,仓库名称和管理员也会一同删除
- 增加一个空仓库,没有指定管理员或者没有任何物品的情况下,可能会出现主键为空的情况。
造成上述情况出现的主要原因是:主属性仓库名称对候选键(管理员,物品名称)是部分依赖的关系。
解决方式: 将表拆分为下面的两张表:
- 仓库表:(仓库名称,管理员)
- 库存表:(仓库名称,物品名,数量) 这样就不存在主属性对于候选键的部分依赖或传递依赖,上面的数据表设计就符合BCNF。
1.6.第四范式(4NF)
如果满足了BC范式,那么就不再会有任何由于函数依赖导致的异常,但是我们还可能会遇到由于多值依赖导致的异常。 多值依赖的概念:
- 多值依赖即属性之间的一对多关系,记为K->->A
- 函数依赖事实上是单值依赖,不能表达属性之间的一对多关系
- 平凡的多值依赖全集U=K+A,一个K可以对应多个A,即K->->A。此时整个表是一组一对多关系。
- 非平凡的多值依赖全集U=K+A+B,一个K可以对应多个A,也可以对应多个B,即K->->A,K->->B。整个表有多组一对多关系,且存在:“一”为相同的属性集合,“多”是互相独立的属性集合。
第四范式即在满足BCNF的基础上,消除非平凡且非函数依赖的多值依赖(删除同一个表中的多对多关系)
1.7.第五范式(5NF)
在满足第四范式的基础上,如果关系模式R中的每一个连接依赖均由R的候选码所隐含,则称此关系模式符合第五范式。
连接依赖: 设关系模式R、Ri的属性集是U、Ui,UiU(1≤i≤n).若R每个容许的实例r均满足r=∏U1(r)∞...∞∏Un(r)则称R满足连接依赖,记作∞(R1,...,Rn).若其中某个Ui=U,则称连接依赖是平凡连接依赖。
函数依赖是多值依赖的一种特殊情况,多值依赖是连接依赖的一种特殊情况。但是连接依赖不能像函数依赖和多值依赖可以由 语义直接导出,而是在 关系连接运算的时候才反映出来。存在连接依赖的关系仍然可能遇到数据冗余插入、修改、删除异常等问题。
第五范式处理的是 无损连接问题,这个范式基本没有实际意义,因为无损连接很少出现,而且难以察觉。而域键范式试图定义一个 终极范式,该范式考虑所有依赖和约束类型,但是实用价值是最小的,只存在理论研究中。
P.S.:第五范式笔者也没理解透彻,因此第五范式不再解释,防止误导诸位,同时如若有对这块十分了解的大佬,可以留言指点一下。
2.反范式化设计
在实际的业务查询中会大量存在着表的关联查询,而表设计都做成了范式化设计(甚至很高的范式),大量的表关联很多的时候非常影响查询的性能。
反范式化就是违反范式化设计:
1、为了性能和读取效率而适当的违反对数据库设计范式的要求;
2、为了查询的性能,允许存在部分(少量)冗余数据
换句话来说反范式化就是使用空间来换取时间。
2.1.范式化和反范式对比
1、范式化的更新操作通常比反范式化要快(字段较少)。
2、当数据较好地范式化时,就只有很少或者没有重复数据,所以只需要修改更少的数据。
3、范式化的表通常更小,所以占据的内存更少。
4、范式化设计的缺点是通常需要关联,稍微复杂一些的查询语句在符合范式的表上都可能需要至少一次关联,也许更多。
5、复杂一些的查询语句也可能使一些索引策略无效。例如,范式化可能将列存放在不同的表中,而这些列如果在一个表中本可以属于同一个索引。
2.2.项目中常见的反范式实现
范式化和反范式化的各有优劣,怎么选择最佳的设计? 那当然是:
2.2.1.缓存与汇总数据
“缓存”来表示存储那些可以比较简单地从其他表获取数据的表。
比如从父表冗余一些数据到子表的。前面我们看到的分类信息放到商品表里面进行冗余存放就是典型的例子。
“汇总”则保存的是使用GROUP BY语句聚合数据的表。
如果需要显示每个用户发了多少消息,可以每次执行一个对用户发送消息进行count的子查询来计算并显示它,也可以在user表用户中建一个消息发送数目的专门列,每当用户发新消息时更新这个值。
在使用缓存表和汇总表时,有个关键点是如何维护缓存表和汇总表中的数据,常用的有两种方式,实时维护数据和定期重建,这个取决于应用程序,不过一般来说,缓存表用实时维护数据更多点,往往在一个事务中同时更新数据本表和缓存表,汇总表则用定期重建更多,使用定时任务对汇总表进行更新。
2.2.2.计数器表设计
计数器表在Web应用中很常见。比如网站点击数、用户的朋友数、文件下载次数等。对于高并发下的处理,首先可以创建一张独立的表存储计数器,这样可使计数器表小且快,并且可以使用一些更高级的技巧。
比如假设有一个计数器表,只有一行数据,记录网站的点击次数,网站的每次点击都会导致对计数器进行更新,问题在于,对于任何想要更新这一行的事务来说,这条记录上都有一个全局的互斥锁(mutex)。这会使得这些事务只能串行执行,会严重限制系统的并发能力。
怎么改进呢?可以将计数器保存在多行中,每次随机选择一行进行更新。在具体实现上,可以增加一个槽(slot)字段,然后预先在这张表增加100行或者更多数据,当对计数器更新时,选择一个随机的槽(slot)进行更新即可。