插曲最近有一个远房亲戚的小表妹来找我,问:“哥,学数据库有前途吗?”我需要从什么开始?”“如果你想学习这门课程,首先要了解数据库的三大范式,比如SQL。”“我大概知道SQL,数据库的三大范式是什么?”“啊……三范式就是表的主键……。唯一的东西,……嗯,应该是那些”“什么是主键?”“呃……表哥,别再问了,请上百度。”“哦……”挂断电话后,我松了一口气。因为差点暴露了我已经不记得三范式的不争事实,我悄悄打开谷歌。。..数据库的三大范式这个概念,相信大部分人都不了解,会比较陌生,从懵懂的大学时代开始就在课本上普及了(没记错的话应该是课本上的IntroductiontoDatabaseSystems).还记得刚开始找实习的时候,我连简历怎么写都不知道,因为太在行了能干,尤其是擅长技术的部分空白。所以我会找隔壁几位顶尖学者的简历做参考。那时候大家的简历上都会说:精通数据库三大范式,精通数据库系统开发语言,或者:熟悉ER图制作工具,能够实现满足三大范式的数据库设计。数据库的三种范式确实是个好东西,以至于技术官在面试的时候没有问到三种范式的细节,让我很意外,也很茫然。随着工作经验的逐渐增长,脑海中对数据库范式理论的强烈印象逐渐消失。我在想,要么是记忆在衰退,要么是某种法则形成了本能的体验。那么,数据库的范式是什么?三大范式的定义这里,我不想花太多篇幅讨论理论上的东西,我可以抓取很多这方面的资料。让我们看一些简单的例子。1、第一种范式假设有一张用户信息表,上面除了用户号和姓名外,还会记录地址信息:NumberNameGenderLocation0001广东省张三南深圳0002海南省李新女,海口市这里里面地址信息列不符合第一范式(1NF):第一范式(1NF):数据库表的每一列都是一个不可分割的原子项。故应拆分为:号码、姓名、性别、省、市0001广东省深圳市张三南0002海南省海口市李新女2.第二种范式以订单为例。通常在淘宝下单,会生成一个包含多个商品的订单,如下:5o2g9Quilt302o2g8Pillow69这也违反了第二范式的定义:第二范式(2NF):每个表必须有一个且只有一个数据元素作为主键(Primarykey),其他属性必须完全依赖主键的第二范式需要基于第一范式。第二范式首先需要一个唯一的主键。上表中订单号和产品号必须作为联合主键才能满足要求。那么第二个要求呢?其他属性是否依赖于这个主键?在订单的场景中,我们可以认为这是合理的,因为价格甚至商品的名称都可能发生变化,但是每个订单看到的信息应该是不变的,没有人愿意看到订单中的商品信息我付钱买的东西突然降价了。当然,更重要的是保持订单总价和产品单价记录的一致性。因此,这里的记录可以看作是订单创建时商品信息的快照。但是,以下情况可能不合适:Order#Item#ItemNamePriceItemCategoryo1g1LaundryDetergent23Homeo1g2HairDryer125Applianceso1g3BroadBeans5Foodo2g9Quilt302Homeo2g8Pillow69HouseholdGoodsThecategory归属一般是固定的,即商品的类别属性只与商品编号相关,即只依赖于主键的一部分。这违反了第二范式中“其他属性必须完全依赖于主键”的规则,所以需要将该属性分离到商品信息表中。3.第三范式让我们回到最开始的user表。如果我们同时在用户信息表中添加一些城市信息:ID姓名性别城市城市特色城市人口0001张三南深圳科技创新1300W0002李斯努海口市旅游观光230W这违反了第三条的定义范式:第三范式(3NF):数据表中的每一列都与主键直接相关,不能间接相关。同样,第三范式也需要建立在第二范式的基础上,显然这里的城市人口、特征等属性只取决于用户所在的城市,而不取决于用户,这只能算是间接关系。因此,最好的做法是将城市相关的属性独立成一张城市信息表。为什么需要范式数据库范式为数据库的设计和开发提供了一个参考模型,也是很多教材中的重点课程内容。那么提出的范式是要解决什么问题呢?第一范式要求对列进行尽可能小的划分,希望消除在某一列中存储多个值的冗余行为。例如,用户表中的地址信息被划分为省、市等特定字段,可以通过独立的字段进行检索查询。第二范式要求主键唯一,不存在对主键的部分依赖。希望消除表中的多余(redundant)列。比如订单表中的产品分类和详细信息,在产品信息表中只需要保存一份即可。第三范式不需要间接依赖于主键的列,即仍然要消除表中冗余的列。例如,不需要存储用户所在城市的人口、城市特征等额外信息。显然,这些范式大多是为了消除冗余而提出的,有利于数据的一致性,当然也可以尽可能地降低存储成本。PS:三大范式你懂的,可以帮老板省钱。怪不得简历上要写..除了本文提到的三种范式,其实还有BCNF范式,第四范式,第五范式。借助三种范式的概念,可以设计出非常精细的数据库表结构。然而,现有的项目应用并没有完全遵循范式的概念,例如:出于性能原因,没有任何冗余的表设计会产生更多的查询行为,这意味着会产生更多的数据库IO操作。在一些实时交互系统中,它可能慢得令人难以忍受。当然,你可以使用数据库连接(join)操作,但实际上数据库提供了join来缓解这个问题。但是一旦采用分库分表的方案,这个问题就会非常棘手。成本结构的变化,数据库范式是在20世纪提出的,当时磁盘存储的成本还很高。随着科学技术的发展,数据存储的成本已经大大降低,采用范式设计(避免冗余)带来的成本降低的好处已经不那么明显了。反范式设计既然范式是消除冗余,那么反范式就是通过增加冗余和聚合来提高性能。例如,为了提高查询性能,作者的信息在CMS的文章表中也是冗余的。冗余的方式会牺牲一定的数据一致性,或者给数据同步带来麻烦,但通常需要业务上的取舍。当然,除了冗余(存储多份)之外,还有一个概念,即数据聚合,或者说嵌套。这种方式相当于将多个字段(列)组合存储到数据库表的一列中。比如一个订单数据可以同时包含很多信息:{"oid":"0001","price":{"total":380,"benefit":40},"goods":[{"gid":"SN001","name":"蓝月亮洗衣液","price":41,"amount":2},{"gid":"SN003","name":"电动剃须刀","price":99,"amount":1}],"address":{"contact":"张三","phone":"150899000"...}...}这种灵活的结构几乎是NoSQL的专利。比如MongoDB文档数据库,可以直接以嵌入式数组和对象的形式实现聚合存储,这无疑带来了极大的灵活性。而MySQL在5.2.7版本开始支持JSON结构列,也进入了聚合存储的行列。与基准PostGreSQL相比,9.4版本已经支持了。反范式的方法在互联网项目和开源产品中也很常见。比如Discuz的数据表设计中有很多冗余的列和聚合字段。一方面,除了提升性能,数据压缩和高度灵活的扩展(非结构化)也是反范式设计能够受到青睐的原因。当然,我们并不是都反对这里的数据库范式。理解范式仍然是良好数据库设计的基础,比如选择合适的主键,明确划分每一列的属性等等。在项目中,还是需要根据自己的业务特点,在范式和反范式(通常是两者的结合)之间找到一个平衡点。类似于架构设计中一些以空间换取时间的做法,需要权衡其中涉及的各种权衡。
