设计流程总览

设计步骤

对于小的应用,数据库的设计者可以直接指明有哪些关系、哪些约束。但对于那些很复杂的现实应用,没人能一下找到实体和关系。因此,需要有清晰的设计步骤来设计一个数据库。

  1. 和数据库的使用者交流,明确所有的数据需求。

  2. 概念设计。E-R 图是表示概念设计的一个工具。这一步要求展示数据库的实体、属性、约束等。

  3. 一个概念模式同样应该满足函数需求。用户指定对数据的各种操作,数据库的设计者确保数据模式能够实现这些操作。

  4. 逻辑设计阶段。设计者把高阶的概念模式转换为数据库系统的数据模型实现。这一步涉及把概念模式转换为关系模式。

  5. 物理设计阶段。物理设计阶段涉及数据库的物理特征,如文件组织和索引结构。

这一设计流程也是数据库大作业要求的流程。只可惜我在完成了大作业后才阅读到这部分,不过幸好,我潜意识中的数据库设计流程和课本上的大差不差。

替代方案

在设计数据库时,需要避免两个陷阱:

  1. 冗余。

  2. 不完全。

冗余的意思是指,一个信息在多个位置储存。

以学校的课程数据库为例,这里假设在实际问题中,同一课程的所有班级的课程名称应该一样。也就是说,100396 这个课号对应的课程名称是 “数据库系统原理”。那么,不管是哪个老师的班级(10039601,10039602…),课程名称应该都是 “数据库系统原理”。

那么,如果课程的名称不仅在课号表储存,还在班号表储存。就会造成同一个信息的冗余。冗余的问题就是不一致性,如果信息在一个地方被修改,在另外一个地方却忘记了修改,这两份本来应该一样的信息就不一致了。

不完全是指,数据库的设计并不能模拟现实场景,从而导致一些功能无法实现。

E-R 模型

E-R 模型可以辅助数据库设计。E-R 模型有三个基本概念:实体集、关系集和属性。当然,E-R 模型也有一个可视化的表达,叫 E-R 图。

实体集

一个实体就是现实世界的一个东西或对象。比如,所有的学生、所有的教授等等。把所有相同类型的个体组成一个集合,就是实体集

在模型设计的过程中,经常用实体集来抽象表示,而不是具体指定一个特定的实体集合。对于实际的实体集合,我们使用实体集的派生这一次来形容。

每个实体都有一系列的属性,比如一个教授可能有工号、姓名、部门等等属性信息。每个实体对于其具有的每一个属性,都应该具有一个值(空值也是值)。

实体集在 E-R 图中用矩形来表示。矩形被分为两部分。上半部分是实体集的名字,下半部分表示实体集具有的属性。主键用下划线标注出来。

关系集

一个关系是一些属性之间的联系。比如,一个指导关系可以是教授和学生之间的关系。一个关系集就是所有同一类别关系的集合。

关系集在 E-R 图中用菱形来表示,菱形分别连接参与这个关系的实体集。最常见的关系是二元关系,当然也可以有更多参与角色的关系。特别地,一个实体集也可以多次参与一个关系,这叫一个循环关系集。比如,在一个前置课程关系中,一门课作为另一门课的前置课程。这里,课程参与了这一关系两次。

一个关系集也可以有一些额外的描述属性。比如,一门学生的课程出了成绩,除了学生和课程这两个参与关系的实体集外,或许我们还在意成绩是什么、成绩什么时候更新的等等额。在 E-R 图中用一个不分割的矩形来表示,矩形和这一关系集用虚线连接。这里的不分割是和实体集的二分矩形相对而言的概念。

关系集具有额外属性

复杂属性

对于一个属性来说,可以有以下的分类:

简单或复合属性。这一分类是按照一个属性是否可以分割成子部来看的。比如说,年龄这个属性就不可再分;但姓名这个属性或许可以分成姓、名、中间名。前者就是简单属性,后者是复合属性。

单值或多值属性。单值指的是这个属性只能有一个值,多值则反之。比如,一个人只能有一个年龄。年龄就是单值属性。但一个人可能有多个电话号码,电话号码就是多值属性。

推导属性。它指的是这个属性可以通过其他属性蕴含得到。比如,有了一个人的出生日期,就能隐式推断出这个人的年龄,而不需要单独一个字段。再比如,一个学生选了多少门课,也不用单独记录,从 takes 关系中这个学生参与的次数就可得知。

映射基数

映射基数主要是衡量一些数学上的关系概念。

首先,映射可以分为:一对一、一对多、多对一和一对多四种。理解起来比较简单。我们以学生和教授的关系为例吧。约定左侧是学生,右侧是教授。

如果一个学生只能选一名教授,反之亦然,那么这就是个一对一的关系。在数学上,这叫既单又满。

如果一个学生只能选一名教授,但一名教授可以指导许多学生,这是一个多对一的关系。反过来,是一对多的关系。

如果对学生和教授的选择数量没有限制,那就是多对多的关系。

这里有个比较容易混的地方。多对多和一对一都很好理解,但是在一对多/多对一关系中,谁是一?谁是多?我觉得从一的角度理解比较容易。一对多,那就是一个人可以有多个选择,也就是分叉了,不是一个函数关系。反过来,多对一,还是从一的那个角度来理解,是反向的一对多。

关系的画法

至于这几个关系怎么画,直接看图吧,不赘述了。但还是要讲一讲一对多/多对一箭头是怎样理解的。刚刚说到,从一的角度来看,这里还是一样。你看,箭头的样子不也是一个从少到多,分叉的形状吗?是呀,哪里分叉了,箭头就在哪里,模拟这个分叉。

除此之外,更加细粒度的分类是,看有多少个实体集参与了这个关系中。比如,每个学生至少选择一名导师,那么,在学生和关系之间的连线就变成了双线,表示实体集中的每个元素都参与了这个关系中。

除了这种粗粒度的表示外,还可以在关系和实体集的连线上显式地把参与关系的实体个数写出来。比如,双线可以用 1..1 来替代,表示一个学生只能参与一次关系。一般地,l..h 表示,一个实体至少参与 l 次关系,至多参与 h 次关系。

上面的一对多、多对多等四个分类,都可以用这里的数字范围来表示。

主键

必须要指定实体或关系如何来识别。

实体集

实体集的键和关系模式的键是一个意思。目的就在于唯一识别实体。

关系集

关系集的超键是所有参与这个关系的实体集的主键的并。但这只是一个超键。

对于一个多对多关系,超键就是主键;对一对多关系,主键是 many 一侧的实体集主键;对一对一关系,哪一侧的作为主键都可以。

对于多元关系,需要限制只有一个箭头,否则会有理解的歧义。具体参见课本吧。最终,在只有一个箭头的情况下,主键是所有没有箭头的实体的主键的并。

弱实体集

这个问题的设计动机是这样的。假设有一个 section 实体,被 course id,semester,year 和 section id 唯一识别。既然 section 和 course 有联系,所以不妨创建一个 sec_course 来把它们关联起来。

看看这个关系中有没有什么冗余?是有的,course_id 是多余的,毕竟 course_id 在 section 中已经出现了。

那么,是去掉这个关系?不好,因为这样的话会存在隐式的关系,即 section 和 course 有关系,但是 E-R 图中却无法体现(因为把这个关系去掉了)。那么,在 section 中去掉 course_id?也不好,因为这样通过剩下的三个属性就无法唯一确定一条记录了。

不过,我们仍然采用第二种处理办法,确实要去掉 course_id,让 section 成为一个弱实体集,它的存在依赖另外一个实体集,这里是 course。course 叫做识别实体集。现在我们使用识别实体集的主键和弱实体集自身的主键来一起识别。后者叫做识别属性

每个弱实体必须依赖某一个识别实体。我们称,识别实体集拥有它识别的弱实体集。弱实体集和识别实体集之间的关系叫识别关系。

具体的 E-R 图如下。

弱实体集的关系

从实体集中去除冗余的属性

设计完了数据库中的实体和属性,之后要考虑它们之间的关系。关系一出现,就可能导致有些实体集中的属性是多余的。

比如说,instructor 具有 dept_name 这个属性,而 department 也有 dept_name。且 dept_name 是 department 的主键。这时候在 instructor 中就要删除这个属性。毕竟,不可能把一个实体集的主键删了。

那,如何表示 instructor 的 dept_name 呢?后续可以用 inst_dept 这个关系来表示。至于这个关系到底是独立出来,还是合并到 instructor 中,看看下一节的内容。

把 E-R 图转换为关系模式

强实体集的表示

这个变化是极为简单的,主键还是主键,属性还是属性。

具有复杂属性的强实体集的表示

对于复合属性,直接拆开。比如,name 使用 first_name,middle_name,last_name 来表示。

对于多值属性,需要有两部分,一部分是原来实体集的主键,另一部分是多值属性。比如,instructor_phone(ID, phone_number)。所有的属性共同组成主键。与此同时,ID 存在一个外键约束。

有的时候,如果原来的实体集只有主键和多值属性这两部分。那么,把多值属性从原来的实体集去除,形成了一个新的关系模式后,原来的实体集就只有主键了。这没啥意义,所以可以把这个实体集删去。采用形成的新的关系模式来表示这个实体就好了。

弱实体集的表示

对于弱实体集,它形成的关系模式包含其识别实体集的主键和自己具有的那些属性。它的主键是强实体集的主键和自己的识别属性。当然,同时还会创建一个外键约束。

关系集的表示

对于一个关系,它的属性由两部分组成:参与这个关系的所有实体集的主键和自己的描述属性(如果有)。

主键的选取,则和上面讲的 ## 主键 部分同理。

模式的冗余

当然,按照之前设计的算法,存在一些冗余。

对于弱实体集来说,识别关系和弱实体集是重复的,更准确的说,是一样的。所以保存一个即可。

模式的组合

此外,对于多对一的关系,因为在”多”那一侧的每个实体至多关联一个”一”那边的属性,所以不需要单独拿出来一个关系集来表示这种关联,直接合并就好。

此外,对于一对一的关系,关系集合合并在哪一个都可以。

换句话说,用培训班的语言,只有多对多的关系需要单独拿出来一个集合来表示这种关系,剩下的直接合并在实体集中,用外键约束来表示就好了。

扩展的 E-R 特性

特化和泛化是两个相对的概念。前者自顶向下,后者自底向上。

特化

一开始有个类叫做 student,我们发现可以把它分成 undergraduate 和 graduate,每个类有自己的特性。

泛化

而泛化是特化的逆,我们先有一些实体,然后提炼他们的共同点,形成父类。

属性继承

特化/泛化造成的一个结果就是属性的继承。子类继承父类的属性,同时继承父类的关系。

当然,子类如果只继承一个父类,叫做单继承;如果继承多个父类,则叫做多继承。其实和面向对象是一样的。

在特化上的约束

在特化上的约束有两个:

  1. 父类是不是必须要属于一个子类(全/偏);
  2. 父类是不是只能属于一个子类(重叠/分立)。

比如说,有个 person,派生出 student 和 employee。第 1 个约束如果是全的,那么 person 必须属于二者其一;否则可以不属于任何一个子类。

如果一个大学需要存储一些临时交流人员,说不定部分特化会好一些。

后者呢,那就是,一个 person 能不能又是 student 又是 employee?还是只能二者之一?

这两个约束是并列的,所以一共可以形成四种约束。

聚合

基本 E-R 图的缺点是无法表示关系之间的关系。聚合就是把关系抽象为一个高阶的实体。从而可以表示关系之间的关系。

转化为关系模式

泛化的表示

表示泛化有两种形式。

如果自顶向下,那么先把父类表示出来。然后,子类只保留父类的主键和额外增加的部分。如:

1
2
3
person(ID, name, street, city)
employee(ID, salary)
student(ID, tot_cred)

子类的主键有一个到父类的外键约束。

对于分立的完全泛化,只需要表示子类。把父类的所有信息和自己的独特属性表示出来。然后,把父类的主键作为子类的主键。

但是这样的话,在外键约束上可能不太好设计,而且对于重叠的泛化,可能有些属性会重复存储。整体给我的感觉是,不如第一个好。

聚合的表示

聚合的表示比较直白。把关系也当成实体就好了。

E-R 设计问题

常见 E-R 图错误

  1. 把一个实体集的主键当做另一个实体集的属性。错误!即使在关系模式中正确,但在 E-R 设计应该用关系来实现;
  2. 不应该把参与关系的实体集的主键作为关系集的属性;
  3. 当应该使用多值属性时,错误地使用了单值属性。

使用实体集还是属性

比如,对于电话号码,到底是把它当做教授的一个简单属性,还是自己独立起来呢?

为什么要独立?独立的好处是可以表示有关电话的更多信息,适用于这种泛化更有益的场景。

使用实体集还是关系集

有时候,可以把关系独立为实体。但是这样好不好呢?看上去直接用一个关系更为精巧,但是有时候,为了储存更多的信息,可能需要把关系独立为个体。

一个可能的指导原则是,用关系集来表示一个行为。

使用二元的还是多元的关系

所有的多元关系都可以拆分成多个二元关系,然而二元关系可能无法表达一些多元关系上的约束。

模拟数据的其他记法

其他的 E-R 图记号

略。

UML 语言

略。

数据库设计的其他方面

功能需求

为功能提供接口,对权限进行管理。

数据流、工作流

工作流指的是进程中数据和任务的组合。

模式进化

一个数据库的设计可能是变化的。数据库的基本约束不应该有变化,但一些临时的约束可能会经常变化。如果发生了变化,比如电子信息与工程学院的计算机系和软件学院新组成了计算机学院。为了应对这种变化做出来的修改应该具有前瞻性,预见到未来可能做出的新修改。

致谢

图片来自于 Database System Concepts 7th edition 一书。