摘要
主要讲解3种关系的数据库模型的设计:
- 一对多:使用外键;
- 多对多:使用关系表,包括2种形式: 纯粹表示关系的中间表,表示中间实体的中间表;
- 一对一:使用外键,并添加唯一性约束;
外键:
是一种约束,外键的存在会在每一次数据插入、修改时进行约束检查,如果不满足外键约束,则禁止数据的插入或修改;
外键只是保证数据的一致性,并不能给系统性能带来任何好处,所以由于外键导致的插入数据变慢会随着数据量的增长而越来越严重。
在实体关系模型中,我们知道有三种关系:一对一、一对多、多对多。这只是概念上的关系,但是在真实的关系数据库中,我们只有外键,并没有这三种关系,那么我们就来说一说在关系数据库管理系统中,怎么实现这三种关系。
一对多
这里先讲解一对多,因为这个关系最简单。一对多和多对一是一回事,所以就不再提多对一这个词。一对多的概念是一个对象A会对应多个对象B,而从B的角度看,一个对象B只会对于一个对象A。比如说班级和学生就是一对多关系。一个班级对应多个学生,一个学生只会对于一个班级。
一对多的关系之所以说简单,是因为RDBMS的外键其实就是表示一对多关系。对于一对多关系,我们只需要在“多”的这个表中建立“一”的外键关联即可,而“一”这边的表不需要做任何修改。比如前面说到的班级学生关系。班级表不变,学生表增加班级Id作为外键。
多对多
多对多的关系在数据库设计时比一对一要常见,所以这里先说说多对多。多对多是一个对象A对应多个对象B,从B角度看,一个对象B也会对应多个对象A。比如说学生和课程的关系就是多对多关系。一个学生会学习多门课程,一门课程会有多个学生来选修。
在RDBMS中,必须使用中间表来表示多对多的关系。中间表我们可以分成两种,一种是纯粹表示关系的中间表,一种是表示中间实体的中间表。
纯粹表示关系的中间表很简单,只需要两列:AID和BID,AID以外键关联到A表的主键,BID以外键关联到B表的主键,然后这两个列组成联合主键。这个中间表纯粹是表示多对多关系而存在,在业务上不会有对应的实体与之对应。比如前面提到的学生和课程的关系,如果我们只需要知道哪些学生上哪些课,哪些课有哪些学生选,不需要有更多的信息的情况下,我们就可以建立“学生课程”中间表,里面只有学生ID和课程ID两个字段。
中间实体是在纯粹的中间关系表的基础上,加上了更多的属性,从而形成了一个新的实体。比如前面提到的学生和课程的关系,如果我们需要记录学生选课的时间、学生选择这门课程后的考试成绩,那么我们就像建立一个“选课”实体,该实体具有如下属性:
选课ID,主键
学生ID,与学生表做外键关联
课程ID,与课程表做外键关联
选课时间,DateTime类型
考试成绩,记录选修该课程后考试的最终成绩
这就是一个中间实体,已经完全脱离了普通的多对多关系中间表,而变成一个实体的形式的存在,所以按照前面博客中讲到的主键设计的原则,我们可以单独建立一个选课ID的列作为数据库的主键,该主键本身并没有业务含义。
一对一
一对一概念上是说一个对象A最多对应一个对象B,从B角度看,也是一个对象B最多对应一个对象A。比如说班主任(教师)和班级的关系,一个班主任最多管理一个班级,一个班级也最多只有一个班主任。
一对一的关系在数据库设计中,是使用的最少的关系,因为一般来说,如果两个实体是一对多关系,那么我们也可以把这两个实体合并成一个实体。但是在设计中,我们仍然会遇到两个完全不同的实体,之间存在一对一关系。
一对一的RDBMS实现是在其中的一个表上建立外键指向另一个表,同时在该外键列上建立唯一约束。比如前面说到的班主任和班级关系,我们可以在班级表建立班主任字段,然后再在该字段建立唯一约束。因为每个班都会有班主任,所以班主任字段是不允许为空的。一个教师可以当某个班的班主任,也可以不当任和班的班主任,同时也不可能在班级表的班主任字段上出现两次,所以最多就当一个班的班主任,所以该设计满足需求。
那么我们可不可以反过来,在教师表中建立所管理的班级Id字段,指向班级表,并建立唯一约束呢?除了不满足“每个班必然有一个班主任”这个业务约束外,其他都没有问题。所以如果对于一对一的情况,如果那边必须要求持有另一边,则就在哪边增加外键字段;如果没有要求必须持有一个另一类实体的话,就哪边添加外键列都行。
外键与索引
外键是一种约束,与索引的概念不一样,只是大多数情况下,我们建立外键时,都会在外键列上建立对应的索引。外键的存在会在每一次数据插入、修改时进行约束检查,如果不满足外键约束,则禁止数据的插入或修改,这必然带来一个问题,就是在数据量特别大的情况下,每一次约束检查必然导致性能的下降。索引其实也有类似的问题,索引如果建多了,那么在插入删除修改数据时也要去维护对应的索引,所以索引的存在也会导致数据操作变慢。
不过外键与索引的优点不同,外键只是保证数据的一致性,并不能给系统性能带来任何好处,所以由于外键导致的插入数据变慢会随着数据量的增长而越来越严重。而索引的目的是为了检索数据更快,维护数据时导致的索引数据的变更,对性能的影响不会像外键那样随着数据量增长而变得严重(当然大数量时的索引树维护会比小数据量的索引树维护更麻烦,但至少不是像外键那样)。
出于性能的考虑,如果我们的系统完全由我们开发的程序使用,而不需要提供数据库给其他应用系统写入数据,而且对性能要求较高,那么我们可以考虑在生产环境中不使用外键,只需要建立能够提高性能的索引。由于整个数据库的操作都是由我们开发的程序来完成的,所以我们程序可以在开发过程中做好各方面的一致性检查,保证操作的数据是满足外键约束的,而不需要真正的存在这样一个外键约束。怎么做到这一点呢,首先,我们在建立数据库时有多个脚本,包括创建表、创建初始化数据、创建索引、创建外键等,我们在开发和测试环境中,都把这些脚本运行了,以使开发测试环境中的数据库是完整的,经过大量测试保证应用程序能够维护数据之间的约束的情况下,那么我们在生产时,并不需要运行创建外键这个脚本文件,只需要创建表、初始化数据、创建索引等即可。