欢迎光临BDM
一枚菜鸟码农的成仙之路

数据库三大范式

作为一名软件开发人员,只要涉及到数据库设计,免不了老生常谈数据库三大范式五大约束,而实际工作中我们常常会遇到范式与现实业务之间的矛盾,那么到底该如何抉择呢?今天我们便从三大范式的基本概念入手,介绍一下数据库三大范式与数据库设计的纠葛。

三大范式简介


第一范式(1NF)

“符合 1NF 的关系中的每个属性都不可再分”。

第一范式可以理解为,一个字段无法包含多个属性,每一个字段需要保证它的原子性。实际上,关系型数据库本身就符合第一范式的要求,即所有字段均单一不可拆分,下表演示了一种不符合的情况,可能经常在页面展示上出现,但是在关系型数据库(RDBMS)里不可能出现。

表1 展示不符合第一范式的数据库设计

姓名 年龄 三围
胸围 臀围
小明 50 40 30
小红 40 30 20

表1中的三围字段里包含了两种属性,不符合第一范式。

正确的修改后应如下表 2 所示:

表 2 符合第一范式的人物属性表

姓名 年龄 胸围 臀围
小明 50 40 30
小红 40 30 20

当然,我们没办法在关系型数据库里,实现不符合第一范式的目标。这是网上流传较广的说法,但我认为有一种设计,也属于不符合第一范式的要求,即将两个字段的内容使用字符串连接符放到一个字段里,如下表 3 所示:

表 3 我认为不符合第一范式的一种设计

姓名 籍贯 手机
小明 021 | 沪 | 上海 18808880888 | 移动
小红 0731 | 湘 | 湖南 19909990999 | 联通

这种情况会带来数据清理的麻烦,并且字段里包含了太多的逻辑,容易出错。但通常这种设计都发生在项目运维的中期,有诸多难言之隐,一般也能接受。


第二范式(2NF)

“消除了非主属性对于码的部分函数依赖”。

如果说第一范式是成立关系型数据库的基础,那么基于第一范式的第二范式可以一定程度上减少数据库的冗余及出错的可能性。网络上关于第二范式的描述众说纷纭,这里选择了一个最晦涩的描述,但也是最根本地讲述了第二范式的基础,通俗易懂的解释为:数据表中每一个属性都要对有且仅有的唯一主键产生完全的依赖关系。

什么是依赖关系呢?如表 4 所示,字段“姓名”能唯一确定对应的“年龄”,也能唯一确定对应的“性别”,但反过来就不行,这里字段“年龄”及“性别”就是直接依赖于“姓名”,这个表也算符合第二范式的标准。

表 4 一般不以姓名当主键,但这里是

姓名 年龄 性别
小明 50
小红 40
小方 40

现在我们扩充一下表4,让其加入更多的内容,如下表 5 所示:

表 5 不符合第二范式的一种设计

姓名 班级 年龄 性别
小明 1 30
小红 1 20
小明 2 10

现在,学校转入了一名新的小明到了 2 班,现在想唯一描述 1 班小明的属性“年龄”与“性别”,需要带上小明的“班级”,如上上一句话所示,而不仅仅通过“姓名”便能指定清楚。这里的“年龄”和“性别”的属性均对“性别”和“班级”产生了“部分函数依赖”,而非之前的对“姓名”产生的“唯一依赖”,所以说这里不符合第二范式的规范。倘若我们需要修改“小明”的属性,我们必须也显式地指定“班级”的属性,亦然给数据库操作的正确性提供了负担。那么符合第二范式的规范该如何设计呢?

为了符合第二范式,我们需要将表5由原先的“姓名”及“班级”组成联合主键的单表分解成两张表,引入“学号”的属性,反转其中的依赖关系,达到我们的目的,如表 6 及表 7 所示:

表 6 学生表

学号 姓名 班级
0 小明 1
1 小红 1
2 小明 2

表 7 学生属性表

学号 年龄 性别
0 30
1 20
2 10

分解成两个表后,“姓名”和“班级”的分别被部分依赖的关系变为了同时依赖“学号”的关系。在“学号”确定的情况下,两个表的其他的属性均能完全确定。这样在修改任一属性时,仅仅需要显式地指定“学号”便能找到唯一的属性进行修改,减少了犯错的可能性。

受水平及时间所限,上述例子比较片面,第二范式的更多理解建议搜索了解。


第三范式(3NF)

“消除了非主属性对于码的传递函数依赖”。

设计数据库时,在满足了第二范式的基础上,我们便需要考虑第三范式的符合与否。第二范式的描述中“部分函数依赖”在第三范式中变成了“传递函数依赖”,那么什么是“传递函数依赖”呢?同样用通俗的一句话对第三范式进行描述:数据表中每一个属性都要对有且仅有的唯一主键产生“直接”的依赖关系。也就是说,不允许非主键成员属性之间产生依赖关系,我们在表 7 的基础上,对第三范式进行演示,如表8所示:

表 8 学生属性表PLUS

学号 年龄 是否成年 性别
0 30
1 20
2 10

表 8 中,新增的字段“是否成年”也属于学生的属性,但是它和“学号”并没有产生“直接依赖关系”,它首先依赖于字段“年龄”——即由“年龄”可以唯一确定“是否成年”,然后由“年龄”依赖于“学号”。“是否成年”和“学号”的关系即为“间接依赖关系”。

在间接依赖的作用下,当我们需要修改学生的“年龄”时,由于其他字段对其产生了依赖关系,我们不仅需要更新“年龄”,还需要根据“年龄”与“是否成年”的逻辑关系来修改“是否成年”的值,这样我们在数据库修改时寸步难行,很难保证数据逻辑正确性。

同样,解决的办法是拆成两个表,将“年龄”与“是否成年”的依赖独立到新的表中,如下表 9 所示:

表9 年龄成年对照表

年龄 是否成年
30
20
10

这样在修改表 7 的“年龄”时,不再需要考虑是否需要修改“是否成年”的属性,非常舒适,当然,前提是表 9 中已经存在对应的“年龄”,因为此时此刻,“年龄”已经成为表 9 的“主键”了。


更多的范式(BCNF、4NF)

除了三大范式之外,数据库有更多的范式对数据库的设计产生了良性的引导,如BCNF、4NF等,由于时间关系,无法在文中一一阐述,有兴趣可以自行学习,对关系型数据库的理解有不小的提升作用。


什么时候不该遵循范式?

虽然范式能为设计库设计提供非常精髓的引导作用,但是在实际的业务使用中,仍然需要根据业务场景来对范式进行适当的“违背”,切不可一味追求准则,实际上大部分的业务遵循第一、二范式就已经不错了。范式的存在旨在于减少因数据库设计不良产生的增删改时的操作错误,同时减少冗余、节约空间,只有把握了这个根本思想,才能对设计进行更好地指导。下面,根据我的主观思想,介绍几种不同的场景,来阐述这个意义。

首先是常见的数据分析及报表业务,有时需要专门的冗余。报表业务中,通常需要对多表的多字段进行联合查询,设计数据量非常庞大并且关联的表很多,这时候如果报表业务直接使用日常业务的表格,将会对日常业务表格的表产生不小的压力——特别是多人同时使用报表的情况下。这时候,我们通常的优化方式是进行主从分离,或者定时将数据放到历史表中。如果原有数据库完美符合几大范式,结合报表业务的特性,查多,增删改没有,我通常采取的方式是专门构建十分冗余的报表性数据表,采用定时任务的方式将原本多表多维度的数据整理到这个表中,牺牲了一定的空间换取查询时的迅速和准确,并且能保留所有的原始数据表。

其次是不常修改的信息表,可以在一个表里描述出业务逻辑并且不常更新的。这些基本的信息表,本身逻辑是多层,但是不常更新,如果分成多表,反而为开发人员多增加了不必要的连接操作,增大程序逻辑复杂的压力。如常见的省市区级联区域表,通常以树状表结构进行存储,每一层的属性里包含了其父类 ID,想要获取完整的地址信息得连接自身三次才能获取完整的信息,甚至为了节约程序数据库IO的次数时,每一个使用到地址信息的地方都要这么连接三次,并且仍然要处理其中的逻辑,增大了开发者不小的麻烦。我们可以直接将省市区分成三个字段分布到一张表中,简单的合并即可拉出地区所有的数据。这一切因为其树状结构有明确的分支层数,并且很少更新,所以不如这样做。

除此之外还有一些存档表之类的等等,均可以不遵守几大范式,因为其使用场景很少能够触碰到几大范式可能遇见的问题。对于现实业务,数据仓库更多的时候存储的意义大于其中物流的意义时,可以反规则而行之。总而言之,数据库范式即不建议对其不知所以,也不建议对其盲从,只有对其深厚的理解加上丰富的经验才能灵活运用其在数据库的设计中。

本文遵守知识共享署名-相同方式共享 4.0 国际许可协议,未经允许不得转载BigDickMan » 数据库三大范式

相关推荐

  • 暂无文章

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

联系我们GitHub