概述一个好的数据库设计不仅能满足数据库用户的需求,而且对应用程序的影响也是非常重大的。然而,数据库设计是一个复杂的过程,好的数据库设计并不是一件简单的事情。对于小型应用程序,了解需求的数据库设计者可能会直接给出要构建的关系、关系的属性以及对其的约束。然而,真实的应用程序往往很复杂,通常没有人能够理解应用程序的所有数据需求并直接给出最终的数据库设计。因此,有必要遵循一种数据库设计方法。数据库设计通常包括以下几个阶段:1.完整描述未来数据库用户的数据需求2.选择数据模型,并利用所选数据模型的概念将需求转化为数据库的概念模式3.转换将数据模型抽象为数据库实现:逻辑设计:将高级概念模式映射到要使用的数据库系统的实现数据库模型物理设计:指定数据库的物理特性,包括文件组织格式和索引结构的选择这文章将主要介绍如何构建数据模型并将数据模型转化为关系模式,以及如何评估关系模式的合理性。将不会有过多的介绍来表征用户的数据需求和物理设计。因为数据需求来自于需求分析,这是软件工程中的一个大过程;物理设计与所选的DBMS有很大关系。概念模型实体关系(E-R)数据模型是数据库中最常用的概念模型。因为它可以将现实世界的意义和交互映射到概念模型上,让技术人员和非技术人员都可以用统一的语言来描述用户的数据需求。本节将首先介绍E-R模型,然后解释如何将E-R模型转换为关系模式。E-R模型介绍E-R模型有三个基本概念:实体集、关系集和属性。本小节首先介绍这三个基本概念,然后解释在E-R模型上定义的一些约束。实体集实体是不同于现实世界中所有其他对象的“事物”或“对象”。比如,公司里的每一个人都是一个实体。每个实体都有一些描述性的属性(称为属性),其中一些可以唯一标识一个实体(称为代码)。例如,工号将唯一标识一名雇员。除了现实世界中真实的事物可以看作是实体之外,一些抽象的事物也可以看作是实体。比如购物单。在E-R图中,一个实体集由一个分为两部分的矩形表示。第一部分包含实体集的名称,第二部分包含实体集中的所有属性,唯一标识实体的属性可以加下划线。实体集是具有相同性质的实体的集合。例如,一个公司所有员工的集合可以定义为实体集employee。有些实体集本身无法在实体集中找到唯一标识单个实体的属性,必须依附于另一个实体才能存在。这个实体集称为弱实体集。比如把stackoverflow上的答案作为一个实体,所有的答案都是一个弱实体集,因为每一个答案都必须依附于问题实体才能存在,其唯一标识属性就是问题ID和答案ID。与弱实体集对应的是那些属性可以唯一标识单个实体的强实体集。弱实体集类似于E-R图中的强实体,只是其唯一标识符属性带有下划线。关系集是多个实体之间的关系。例如,项目与开发者之间的链接develop,表示该项目是由哪些开发者开发的。联系人也可以具有描述性属性。例如,develop链接可以添加startsAt属性来指示开发者何时开始加入项目。关系集是相同类型的关系的集合。关系集在E-R图中由菱形表示,而与弱实体集关联的关系集由双菱形表示。关系集的每个属性都放在一个矩形中,通过虚线连接到关系集。属性我们在引入实体集时引入了属性。下面介绍一下属性的分类:简单属性和复合属性:不能再分解成更小部分的属性称为简单属性,可以进一步分解成更小部分的属性称为复合属性。例如DBMS的类型是一个简单属性,地址是一个复合属性,可以分为街道名称、门牌号等。单值和多值属性:对于一个特定的实体,如果一个属性只有一个值,称为单值属性,否则称为多值属性。比如一个人的身份证号码是单值的,但是他的手机号码是多值的。派生属性:此类属性的值可以从其他相关属性或实体派生。例如,如果一个人的信息包括出生日期和年龄,那么年龄就是派生属性。约束只有实体和联系,不能完整描述真实事物之间的关系,例如一个实体通过关系集与另一个实体相关联的数量,以及一个实体参与连接的数量。映射基数用于表示一个实体通过关系集关联到另一个实体的个数,必须是以下四种情况之一:一对一:实体集A中的一个实体至多与一个实体相关在实体集B中链接,反之亦然。一对多:实体集A中的一个实体可以与实体集B中的任意一个实体相关联,B中的一个实体最多只能与A中的一个实体相关联。多对一:实体集A中的一个实体最多与实体集B中的一个实体相关联,B中的一个实体可以与A中的任意数字相关联。多对多:实体集A中的一个实体可以与实体集B中的任意一个数相关联,B中的一个实体可以与A中的任意一个数相关联。参与关系的实体集的个数由参与约束来描述。如果实体集E中的每个实体至少参与关系集R的一个关系,则称实体集E全部参与关系集R;如果E中只有部分实体参与了R的关系,则称实体集E是关系集R的部分参与者。转化为关系模式的E-R模型是现实世界在其上的意义和交互的体现概念模型,而关系模型是对数据库中所有数据的逻辑结构和特征的描述。因此,将E-R模型转换为关系模型是一个里程碑阶段,之后最终的数据库表结构非常接近。下面介绍具体的转换方法。简单属性强实体集的表示简单属性强实体集与对应的关系模式的属性是一一对应的,强实体集的主键是关系模式。比如一个公民的实体集有两个属性:身份证ID和姓名,那么对应的模式就是:chinese_public(ID,name)属性复杂的强实体集的表示,对于强实体的改造稍微复杂一点具有复杂属性的实体集一点:对于复合属性,你不会直接自己创建一个属性,而是将它的所有简单属性添加到关系模式中。对于多值属性,我们将创建一个新的关系模式。新关系模式中的元组对应一个值,并使用多值属性所在的实体集的主键关联。对应于派生属性,我们没有在关系模式中明确表达它们。弱实体集的表示弱实体集到关系模式的转换与强实体集类似,只是它的主键包括它所依赖的实体集的主键和它自己的鉴别器。由关系集转换而来的关系模式的属性表示其自身属性与参与该关系集的所有实体集的主键的并集。关系模式中主键的选择分为以下几种情况:对于多对多的二元关系,将参与实体集的主键合并为主键。对于一对一的二元关系集,可以选择任意实体集的主键作为主键。对于一对多或多对多二元关系集,关系集中“多”实体集的主键成为主键。对于n元关系集,将关系集中所有不为“一个”的实体集的主键并集合并为主键。将关系集转换为关系模式时,关系模式的数量小于关系集的数量。这是因为模式的冗余和模式的合并。模式冗余考虑一个弱实体集,它本身包含它所依赖的强实体集的主键。如果弱实体集和它所依赖的强实体集的关系集没有其他属性,那么出现在关系集中的所有属性都会出现在弱实体集中。因此,由弱实体集转换而来的关系模型包含了由关系集转换而来的关系模型的所有属性。在这种情况下,不需要为关系集给出对应的关系模式。模式合并考虑从实体集A到实体集B的多对一关系集AB。按照前面的方法,我们将得到三个关系模式:A、B和AB。那么我们可以将A和AB的schema合并成一个schema,这个schema包含两个schema的所有属性的并集,合并后的schema的主键就是A的主键。如果A全参与,那么B的属性在合并模式中都是有价值的;否则A中不参与关系集的元组在合并模式对应的元组中,B中的属性为NULL。对于一对一的关系集,其关系模式可以合并到任何实体集中。Normalization介绍了E-R模型,以及如何将E-R模型转化为关系模型,但是得到的关系模型是不是好的设计呢?答案显然不一定,如果E-R模型本身质量不高,那么大概率得到的关系模型质量也不高。要回答这个问题,我们首先需要明确什么样的设计是好是坏,然后才能做出评价。对于一个糟糕的设计,我们需要给出一个方法来把它变成一个好的设计。糟糕的设计这是一张图书馆借书表,所有信息都存储在这张表中。这张表有什么问题?首先是数据冗余的问题。如果一个人多次借过多本书,这个人的信息就会重复多次;如果一本书被多人借过,这本书的信息也会重复多次。而数据冗余会带来数据一致性的问题。修改一个人的信息需要更新他所有的借款记录;修改一本书的信息还需要更新包括这本书在内的所有记录。其次是数据完整性问题。如果图书馆会员没有借过书,他的信息将不会被保存;如果没有借过新书,则不会保存这本书的信息;如果只有他借了这本书,那么这本书上的信息也会随之消失。按应用程序范式规范化已经用一个例子来说明,糟糕的设计会存在数据冗余和完整性问题。将通过下面的范式进行归一化,以消除这些问题。第一范式第一范式要求每一列的取值范围由原子值组成,每个字段的值只能是单个值,每一行需要有一个主键。以前面的例子为例,为了满足第一范式,我们需要将借出的图书和图书类别的多个值放到不同的行中,并以ID和借出的图书作为主键(假设书名不再重复)。下面是修改后的结果:显然,第一范式只能让表格看起来更好看,并没有实质性的解决之前的问题。因此,接下来您需要添加约束。第二范式第二范式需要两点:满足第一范式非主属性在功能上完全依赖于所有主属性下面是几个需要解释的名词:非主属性:不包含的属性在主键中。主属性:主键中包含的属性。完全函数依赖:如果属性集X和属性集Y之间存在函数关系X->Y,并且对于X的任意真子集X',X'->Y不成立,则称Y完全泛函对X的依赖。可能有人会认为全功能依赖的解释看起来和没有解释一样,所以这里用上面的例子来说明。如上所述,我们使用ID和借出的书作为主键。一旦确定了ID和借出的书,姓名和居住地也就确定了,即函数(ID,借出书)->(姓名,居住地)成立。但是,如果确定了ID,那么也可以确定姓名和居住地,所以对于(ID,??bookslended)的真子集(ID),函数(ID)->(name,placeofresidence)仍然成立。所以上表不满足第二范式。我们需要将这张表拆分成membership表,booktable,bookcurrentlendingtable:第三范式从上面的例子来看,通过第二范式的改造,解决了上面提到的问题。那么为什么会有第三范式呢?首先,我们来看一个满足第二范式但仍然存在前面问题的例子。这个关系模式的主键是书名,显然满足第一范式。因为主键只有一个字段,非主属性必然是对所有主属性的完全函数依赖,所以也满足第二范式。但是如果我们在这张表中加上一个人民邮电出版社出版的《深入理解 MySQL》,我们会发现出版社的地址就多余了!为了消除这种冗余,第三范式被提出。第三范式需要满足以下两点:满足第二范式非主属性对主键没有传递函数依赖。什么是传递函数依赖?这里不给出正式的定义,而是用前面的例子来说明。在前面的例子中,因为书名是主键,所以函数(书名)->(出版商)成立。同时我们知道发布者地址是确定性的,所以函数(发布者)->(发布者地址)也成立。所以有(书名)->(出版商)->(出版商地址)成立。这时候我们也说出版商地址功能取决于书名。为了消除非键属性对主键的传递函数依赖,我们将上表拆分为两张表:BCNF中上面提到的第二范式和第三范式分别消除非键属性的部分函数依赖-key属性对主属性和传递依赖,如果主属性对主键有部分函数依赖和传递函数依赖怎么办?例如,假设:一家数据库公司为多个甲方提供技术支持,而甲方公司现场只需要一名技术支持人员,一名技术支持人员只能支持一个甲方公司在一个甲方公司使用多个产品数据库公司,每个产品维护时间不一样,所以考虑现场支持关系模式(甲方公司,技术支持人员,项目名称,支持时间),其主键是甲方公司,技术支持人员,和项目名称。它满足第三范式,因为不存在非主属性对主键的偏函数依赖和传递依赖。但仍然存在问题:如果技术支持人员调到另一家甲方公司,那么与该技术人员相关的所有记录都会被修改;同时,如果甲方公司和数据库公司有多个项目,那么甲方公司和技术支持人员都需要重复。因此,满足第三范式的关系模式未必能完全解决前面的问题。针对这个问题,BCNF被提出,这里给出它的要求:满足第三范式中的主属性,不存在对主键的部分函数依赖和传递依赖在上面的例子中,主属性(PartyA的公司)部分取决于主键(技术支持人员、项目名称)。修改后的关系模型为:总结回顾一下E-R关系模型和范式,其实我们可以发现:不满足第一范式的关系模型可能根本就没有建立E-R模型,直接把所有的信息都放到一张桌子;或者E-R模型没有主键;或者E-R模型转关系模型时,多值属性和复合属性处理不当,不符合其他范式。原因应该是没有正确识别实体集和关系集。例如,在第二范式的例子中,成员实体集和书籍实体集的信息完全混合在一起;在第三范式中,图书实体集和出版商实体集混合在一起;BCNF例子更可能来自实体集甲方公司,技术支持人员和项目的三元关系集。此外,如果E-R模型的质量很高,那么生成的关系模式满足更高级别范式的概率就会大得多。我的工作经验有限,没有真正体验过完整的建模和标准化。刚从有经验的人那里了解到,大部分业务公司都会做建模,标准化的比较少。这大概是因为他们的模型建立的很好(:。当然,实际的数据库设计不一定非得满足范式。比如有时候为了业务的方便,也会选择一些数据冗余。综上所述,本文只介绍了数据库设计的一小部分,这一小部分对于一个高质量的数据库设计来说非常重要,但还远远不够。要想得到一个高质量的数据库设计,还需要从两个方面下功夫一方面,在E-R模型的上游需要做好领域模型的构建,从而对需要构建的系统有更深入的了解,从而获得一个更高质量的E-R模型。另一方面,需要对所使用的DBMS的特性有很好的了解。例如,使用MySQL,你需要知道哪种类型的存储引擎存储是合适的,什么类型的字段是否合适等;有些人e也建议不要使用关系模式的主键,而是使用自增主键。这两个方面并不是完全隔离的,比如应该根据查询来考虑为哪些字段创建索引,而最常见的查询其实可以在业务建模时弄清楚。总的来说,数据库设计是一个系统工程,需要对整个系统有详细的了解才能做好。
