数据库设计(三范式,冗余字段,逆范式)
在设计数据库的时候,应该注意一下什么呢?首先来看一张表。
学生成绩表
姓名 | 性别 | java成绩 | c成绩 | 班级 |
张三 | 男 | 90 | 80 | 一班 |
李四 | 女 | 80 | 90 | 一班 |
杨过 | 男 | 90 | 80 | 一班 |
思考:上面这张表在设计上会出现什么问题?
作为初学者的我看到这章表,我的回答是不会出问题,其实从现实生活中来讲,这的确没什么问题的,记得念中学的时候,每次考完试,心里都很着急,急什么呢?当然是成绩,想想那个时候老师又是怎样把你的成绩信息给列出来的呢?是不是以这样的方式,每个班,甚至整个学校的学生成绩都是使用一张表来列出来的,而我们每个人的成绩利用该表中的一行数据来表示的,其中包括一个学生的学号,姓名,班级,成绩以及排名等等。而这里我们只是专业化成了数据库,一个班级的学生成绩变成了数据库中的一张表,而每个学生的成绩变成了表中的一条记录而已,所以认为没什么问题。但是对数据库的设计来讲,概念就不一样了,这样设计就会很容易出现很多问题。会出现一下什么问题呢。比如像增加异常,删除异常,更新异常等等,下面是几个例子。
eg1:在增加数据的时候,可能发生错误。这叫增加异常
姓名 | 性别 | java成绩 | C成绩 | 班级 |
张三 | 男 | 90 | 80 | 一班 |
李四 | 女 | 80 | 90 | 一班 |
杨过 | 男 | 90 | 80 | 一班 |
小龙女 | 女 | 100 | 70 | 1班 |
再仔细看上面这张表,在插入最后一条数据的时候,其中插入的班级和前面的不同,即“1班”,这从业务上来讲,当然没什么问题,但是从数据库的角度来讲,确是两个完全不同的概念,你可以认为是数据库本身太笨了吧,所以这个时候插入的数据,会给我们带来不必要的错误,这种错叫做增加异常。
eg2:在删除数据时,可能会删除相关的其它数据,这叫删除异常
这句话怎么理解呢?同样,看上面eg1的表,如果我们现在要指定删除班级为“一班”的班级怎么办?这个时候我们使用delete 学生成绩表 where 班级='一班' 的话,就删除了班级='一班'的这些记录,这个时候,不是也给我们带来了不必要的后果吗?这就是删除异常。
eg3:在更新数据的时候,可能会有数据没有更新到。这叫更新异常。
姓名 | 性别 | java成绩 | C成绩 | 班级 |
张三 | 男 | 90 | 80 | 1班 |
李四 | 女 | 80 | 90 | 1班 |
杨过 | 男 | 90 | 80 | 一班 |
那如何理解更新异常呢?先了解上面表在做什么吧,上面表是在修改班级,我想把班级名称全部改成“1班”,这个时候我会使用update语句来一条一条的修改,但是如果数据有很多很多的话,这个时候我们有可能忽略了某个地方,导致没有修改到,所以在我执行某型操作的时候,就会出现数据与我们想象的不一致,这种结果就是所谓的更新异常导致的。上面举了几个例子,说明了在数据库中是很容易出现问题的,而这些问题从业务上来讲,我们很难查找出来,那导致这样的问题原因是什么呢?有两点:
1.看上面的表,把他们的数据全部耦合到一起了。
2.因为我这样耦合的设计,导致产生了许多重复的数据,比如班级列,我就添加了三条一模一样的,也就是一班。解决方法:根据实体和关系,我们可以把他们保存在不同的表中,然后可以利用一个外键,来指向我们所需要的数据。据上面例子,我们可以建立两张表:
表1:
班级 | 班级编号 |
一班 | 001 |
二班 | 002 |
表2:
学号 | 姓名 | 班级编号 |
0103 | 张三 | 001 |
0104 | 李四 | 001 |
0201 | 杨过 | 002 |
0202 | 小龙女 | 002 |
这张表,就是把班级和学生信息分开来保存了,这个时候,班级和学生之间的关系是一对多,所以在数据库设计的时候,我们可以将一对多中,多的那一方设置一个外键列,这里当然是学生是多的这方,所以我们在表中有一列叫做“班级编号”的外键列,然后利用外键列去引用我们的班级表中的“班级编号”列,从而建立起他们一件的那种一对多的关系。上面的问题是解决了,但是在以后我相信还会遇到相同的问题,也就是会遇到什么增加,删除或更新异常等,这就代表了以后的数据“难以维护”。所以我们要对数据库进行适当的设计,才可以大大的增加可维护性。那么进行数据库设计,有六个范式,一般说来,数据库只需满足第三范式(3NF)就行了。什么是范式呢?为了将各种信息进行有效的分类整理,就设计了一套行之有效的数据库设计原则,这叫做范式,英文normal forms,意思是规范化表。也表示将表进行分类处理,这个过程就叫做规范化。简单理解,规范化表的过程,就是将数据进行分类的过程。下面说说这三个范式。
1 第一范式(1NF)
在任何一个关系数据库中,第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系数据库。所谓第一范式(1NF)是指数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。如果出现重复的属性,就可能需要定义一个新的实体,新的实体由重复的属性构成,新实体与原实体之间为一对多关系。在第一范式(1NF)中表的每一行只包含一个实例的信息。例如,对于员工信息表,不能将员工信息都放在一列中显示,也不能将其中的两列或多列在一列中显示;员工信息表的每一行只表示一个员工的信息,一个员工的信息在表中只出现一次。简而言之,第一范式就是无重复的行,并且列不可再分。
2 第二范式(2NF)
第二范式(2NF)是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)。第二范式(2NF)要求数据库表中的每个实例或行必须可以被惟一地区分。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。员工信息表中加上了员工编号(emp_id)列,因为每个员工的员工编号是惟一的,因此每个员工可以被惟一区分。这个惟一属性列被称为主关键字或主键、主码。第二范式(2NF)要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。简而言之,第二范式就是非主键列必须依赖于主键列。第二范式(2NF)值得注意的是:“NF2要求在数据库设计时,每个表中的信息,只能是自己实体所拥有的信息。”
eg:属性1必须依赖于属性2:属性1,一定是属性2所代表的实体“天生”具有的。
学号 | 姓名 | 兴趣1 | 兴趣2 |
1 | a | 打球 | 打豆豆 |
2 | b | 开车 | 开玩笑 |
对这张表来说就是:姓名必须依赖于学号:姓名,一定是学号所代表的实体“天生”具有的如果想这张表不是属于学号所代表的实体,即学生,那么就应该将学生信息和兴趣分开来建表保存。数据库设计时,基本上都要满足NF2.那什么时候会违反NF2呢? 有一个需求,要求查询:学号,姓名,班级名称。由于学生姓名和班级名称分开在2个中,所以在查询时,必须使用表连接。代码:select * from 班级表 c , 学生表 s where c.班级编号=s.班级编号。但是表连接会让计算机做大量的匹配和计算,所以是比较消耗资源(资源主要是指CPU和内存)。为了提高效率,所以可以违反NF2,将表格如下:
班级表 | |
班级编号 | 班级名称 |
学生表 | |||
学号 | 姓名 | 班级编号 | 班级姓名 |
现在查询就只需要查询一个表了,从而避免了表连接!学生表中的“班级名称”是第2次出现,所以叫做“冗余字段”,这种设计方法叫做“逆范式”!在本质上,实际上就是牺牲空间,换取时间。
3 第三范式(3NF)
在满足了第一范式和第二范式的基础只上,可以满足第三范式,第三范式简而言之,就是非主键列之间是相对独立的。先看一张表。
学号 | 姓名 | 出生日期 | 生肖 |
1 | 张三 | 1980-01-02 | 猴 |
2 | 李四 | 1983-04-02 | 牛 |
3 | 王五 | 1988-11-02 | 牛 |
生肖表
生肖编号 | 生肖名称 |
1 | 猴 |
2 | 牛 |
这里的生肖其实是和时间有关系了,可以通过时间来确定生肖是什么,所以这里的生肖完全可以忽略不计,一般这样设计是为了减轻数据库的计算压力,所以才保留此字段的,也就是前面提到的“冗余字段”,逆范式。所以由此可以推出3NF是完全可以违反的。最后总结一下前三个范式那些必须满足:
NF1 行不可重复,列不可再分 - 必须满足
NF2 非主键列必须依赖于主键列 - 必须满足
NF3 非主键列之间必须相互独立 - NF3可以不满足