数据库有几个范式,但是在此我只讲解第一范式、第二范式、第三范式。因为,我从业多年以来设计数据库完全遵守第三范式已经能够满足绝大多数系统的要求。(甚至,有些时候为了降低开发难度并提高效率,我们需要在某些表设计时进行冗余,牺牲掉范式要求。)
我们为什么会要求数据库设计时要基本满足第三范式呢?因为要减少数据存储冗余、同时降低数据的不一致性。现在, 请出栗子先生。
还是应用经典的选课系统的一部分来讲解这个问题:
需求:假设某学校有一个选课系统,学校下设学院,学院下设专业。学校开了很多课程(离散数学、关系数据库、数据结构、Web系统开发)。每个学生需要自己手动选课,(假设都是选修课程:多么美好&&)。每个学生可以选0-2门课程。需要我们设计数据库,进行开发。
看看同学 贾宝 设计的数据库:
表1:学院表
序号 | 字段名 | 类型 | 长度 | 是否主键 | 是否可空 | 说明 |
1 | 学院编号 | int | 8 | 是 | 否 | |
2 | 学院名称 | varchar | 60 | 否 | 否 | |
3 |
……未全部显示
表2:学生表
序号 | 字段名 | 类型 | 长度 | 是否主键 | 是否可空 | 说明 |
1 | 学号 | int | 8 | 是 | 否 | |
2 | 姓名 | varchar | 30 | 否 | 否 | |
3 | 所属学院名称 | varchar | 60 | 否 | 否 | |
4 | 所属专业名称 | varchar | 60 | 否 | 否 |
上边是宝同学设计的数据库中的学生表。这个表满足第二范式,但是不满足第三范式。这样会带来数据冗余与不一致性。这是该数据库的两条记录:
(10001,'计算机科技与信息工程学院',……),(20090101,'柳明','计算机科技与信息工程学院',‘计算机科学',……)。
上面记录代表的意思该学校存在编号10001的计算机学院,学号为1000101的柳明同学在该学院,专业是计算机科学。
首先,这种记录方式是冗余的:
其中的(1000101,'柳明','计算机科技与信息工程学院',‘计算机科学',……),可以用:(20090101,'柳明',10001,1000101,……)来代替(其中的10001是学院编号,1000101是专业编号)。这样的话,学院编号与专业编号如果用int数字类型保存的话需要2个字节,而使用varchar保存就需要几十个字节。而且由于是使用字段名称记录,在进行表关联查询时,会使得查询效率降低。
其次,这种记录方式会带来不一致性:
如果有一天,学校对该学院增加了机器人、大数据AI等专业。为了提高计算机学院的标识度,学院要更名为计算机与人工智能学院。负责更名选课系统模块的程序员的逻辑是这样写的:(采用伪代码表示,不考虑数据类型检查等逻辑)
funtion updateCollege() {
获取页面传递过来的id;
根据id查找该条学院记录;//请看这一行
用所有页面传递过来的字段更新该记录;
将该记录回写数据库;
返回操作结果;
}
请看加粗行,该程序只更新了学院记录。那么就会造成学生中学院记录没有更新。经过更新后,数据记录是这个结果:
(10001,'计算机与人工智能学院',……),(20090101,'柳明','计算机科技与信息工程学院',‘计算机科学',……)。
上面结果中学院名称出现了不一致。那么,宝同学说,可以将程序改一改,呢,这样:
funtion update() {
获取页面传递过来的id;
开始事务;
根据id查找该条学院记录;//请看这一行
updateCollegeOfStudents(id);
用所有页面传递过来的字段更新该记录;
将该记录回写数据库;
提交事务;
返回操作结果;
}
function updateCollegeOfStudents(int id){
根据id查找该条学院所有学生记录;//请看这一行
循环修改所有学生记录中学院名称;
将所有记录回写数据库;
返回操作结果;
}
他说的可以解决问题,但是违反了程序设计中的一个原则:方法或者类的单一职责原则,进而导致代码耦合度过高。这样会降低开发效率,导致逻辑性bug频出。
那么有没有解决之道呢?这样设计:
表1:学院表
序号 | 字段名 | 类型 | 长度 | 是否主键 | 是否可空 | 说明 |
1 | 学院编号 | int | 8 | 是 | 否 | |
2 | 学院名称 | varchar | 60 | 否 | 否 | |
3 |
……未全部显示
表2:专业表
序号 | 字段名 | 类型 | 长度 | 是否主键 | 是否可空 | 说明 |
1 | 专业编号 | int | 8 | 是 | 否 | |
2 | 所属学院编号 | int | 8 | 否 | 否 | 外键 |
3 |
表3:学生表
序号 | 字段名 | 类型 | 长度 | 是否主键 | 是否可空 | 说明 |
1 | 学号 | int | 8 | 是 | 否 | |
2 | 姓名 | varchar | 30 | 否 | 否 | |
3 | 所属专业编号 | int | 8 | 否 | 否 | 外键 |
4 | varchar | 60 | 否 | 否 |
……未全部显示
下面是这个学生有关的记录:
(10001,'计算机科技与信息工程学院',……);(1000101,10001,‘计算机科学',……)(20090101,'柳明',1000101,……)。
这几条记录表示,该学校有一个编号10001的计算机科学与信息工程学院,该学院设置了编号1000101的计算机科学专业,学号20090101的柳明同学是该专业的学生。
这样就解决了冗余性与不一致性方面的问题,具有低冗余度高一致性。同时这样的数据库设计编写的程序也更大可能会具有高内聚低耦合的特性。那这是为什么呢,因为这个设计满足了第三范式。只要按照第三范式去做,就可以做到较好的数据库设计。
下一节将讲解具体的第三范式,该系列文章目录:
系列目录