引言
MySQL的库表设计,在很多时候我们都是率性而为,往往在前期的设计中考虑并不全面,同时对于库表结构的划分也并不明确,所以很多时候在开发过程中,代码敲着敲着会去重构某张表结构,甚至大面积重构多张表结构,这种随心所欲的设计方式,无疑给开发造成了很大困扰。
但实际上设计DB库表结构时,也有一些共同需要遵守的规范,这些规范在数据库设计中被称为“范式”,理解并掌握这些设计时的规范,能让咱们在项目之初,设计的库表结构更为合理且优雅。数据库范式中,声名远扬的有三大范式,但除此之外也有一些其他设计规范,如:
-
①数据库三大范式(1NF、2NF、3NF)
-
③第四范式(4NF)和第五范式:完美范式(5NF)
-
②巴斯-科德范式(BCNF)
-
④反范式设计
不过对于上述的几种设计范式,大部分小伙伴应该仅了解过三范式,对于其他的应该未曾接触,那在本篇中会重点阐述库表设计时,会用到的这些范式。
一、数据库三大范式
范式(Normal Form)在前面也提到过,它就是指设计数据库时要遵守的一些原则,而数据库的三大范式,相信诸位在学习数据库知识时也定然接触过。三大范式之间,它们是递进的关系,也就是后续的范式都基于前一个范式的基础上推行,就好比下面这句话:
今天我要先炒菜,然后吃饭,最后洗碗。
炒菜、吃饭、洗碗三者也属于递进关系,后者都建立在前者之上,其顺序不能颠倒,比如先吃饭再炒菜,这必然是行不通的。数据库的三大范式也一样,第二范式必须建立在第一范式的基础之上,如若设计的库表第一范式都不满足,那定然是无法满足第二范式的。
写在前面的话:其实对于数据库三范式相关的资料,网上也有很多很多,但大部分资料都涉及了太多的概念,通篇看下来也很难让人理解,因此下述的三范式则会结合具体的设计实例来让诸位彻底理解三范式。
1.1、第一范式(1NF)
库表设计时的第一范式,主要是为了确保原子性的,也就是存储的数据具备不可再分性,这话咋理解呢?上个案例:
SELECT * FROM `zz_student`; +----------------------+--------+-------+ | student | course | score | +----------------------+--------+-------+ | 竹子,男,185cm | 语文 | 95 | | 竹子,男,185cm | 数学 | 100 | | 竹子,男,185cm | 英语 | 88 | | 熊猫,女,170cm | 语文 | 99 | | 熊猫,女,170cm | 数学 | 90 | | 熊猫,女,170cm | 英语 | 95 | +----------------------+--------+-------+ 复制代码
在上述的学生表中,其中有一个student学生列,这一列存储的数据则明显不符合第一范式:原子性的规定,因为这一列的数据还可以再拆分为姓名、性别、身高三项数据,因此为了符合第一范式,应该将表结构更改为:
+--------------+-------------+----------------+--------+-------+ | student_name | student_sex | student_height | course | score | +--------------+-------------+----------------+--------+-------+ | 竹子 | 男 | 185cm | 语文 | 95 | | 竹子 | 男 | 185cm | 数学 | 100 | | 竹子 | 男 | 185cm | 英语 | 88 | | 熊猫 | 女 | 170cm | 语文 | 99 | | 熊猫 | 女 | 170cm | 数学 | 90 | | 熊猫 | 女 | 170cm | 英语 | 95 | +--------------+-------------+----------------+--------+-------+ 复制代码
将student这一列数据,分别拆分为姓名、性别、身高三列,然后分别存储对应的数据才合理,通过这样的优化后,此时zz_student这张表则符合了数据库设计的第一范式。
那此刻思考一下:如果不去拆分列满足第一范式,会造成什么影响呢?
-
客户端语言和表之间无法很好的生成映射关系。
-
查询到数据后,需要处理数据时,还需要对student字段进行额外拆分。
-
插入数据时,对于第一个字段的值还需要先拼装后才能进行写入。
简单来说,如果按照原本那张形式去做业务开发,显然操作起来会更加麻烦且复杂一些,但第一范式的原子性,除开对列级别生效之外,行级别的数据也是同理,也就是每一行数据之间是互不影响的,都是独立的一个整体。
1.2、第二范式(2NF)
上述的第一范式还是比较容易理解,紧接着来看看第二范式,第二范式的要求表中的所有列,其数据都必须依赖于主键,也就是一张表只存储同一类型的数据,不能有任何一列数据与主键没有关系,还是上面的那张表数据为例:
+--------------+-------------+----------------+--------+-------+ | student_name | student_sex | student_height | course | score | +--------------+-------------+----------------+--------+-------+ | 竹子 | 男 | 185cm | 语文 | 95 | | 竹子 | 男 | 185cm | 数学 | 100 | | 竹子 | 男 | 185cm | 英语 | 88 | | 熊猫 | 女 | 170cm | 语文 | 99 | | 熊猫 | 女 | 170cm | 数学 | 90 | | 熊猫 | 女 | 170cm | 英语 | 95 | +--------------+-------------+----------------+--------+-------+ 复制代码
虽然此时已经满足了数据库的第一范式,但此刻观察course课程、score分数这两列数据,跟前面的几列数据实际上依赖关系并不大,同时也由于这样的结构,导致前面几列的数据出现了大量冗余,所以此时可以再次拆分一下表结构:
SELECT * FROM `zz_student`; +------------+--------+------+--------+--------------+--------------+ | student_id | name | sex | height | department | dean | +------------+--------+------+--------+--------------+--------------+ | 1 | 竹子 | 男 | 185cm | 计算机系 | 竹子老大 | | 2 | 熊猫 | 女 | 170cm | 金融系 | 熊猫老大 | +------------+--------+------+--------+--------------+-------------