Skip to content

数据库设计

实际应用中,需求分析阶段需要做什么?

在《数据库设计步骤》一节我们介绍了设计数据库的基本步骤,下面将详细介绍需求分析阶段。

需求分析阶段的重点是调查、收集并分析客户业务的数据需求、处理需求、安全性与完整性需求。

常用的需求调研方法有在客户的公司跟班实习、组织召开调查会、邀请专人介绍、设计调查表并请用户填写和查阅与业务相关的数据记录等。

常用的需求分析方法有调查客户的公司组织情况,各部门的业务需求情况、协助客户分析系统的各种业务需求和确定新系统的边界。

无论数据库的大小和复杂程度如何,在进行数据库的系统分析时,都可以参考下列基本步骤:

  1. 收集信息
  2. 标识实体
  3. 标识每个实体需要存储的详细信息
  4. 标识实体之间的关系

1. 收集信息

创建数据库之前,必须充分理解数据库需要完成的任务和功能。简单来说,就是要了解数据库中需要存储哪些信息(数据),实现哪些功能。

下面以酒店管理系统为例,我们需要了解酒店管理系统的具体功能,以及在后台数据库中保存的数据,如以下需求:

  • 酒店为客人准备充足的客房,后台数据库需要存放每间客房的信息,如客房号、客房类型、价格等。
  • 客人在酒店入住时要办理入住手续,后台数据库需要存放客人的相关信息,如客人姓名、身份证号等。

2. 标识实体

在收集需求信息后,必须标识数据库要管理的关键对象或实体。实体可以是有形的事物,如人或产品,也可以是无形的事物,如商业交易、公司部门或发薪周期。

在系统中标识这些实体以后,与它们相关的实体就会条理清楚。以酒店管理系统为例,我们需要标识出系统中的主要实体。

  • 客房:单人间、标准间、三人间、豪华间和总统套房。
  • 客人:入住酒店客人的个人信息。

注意:实体一般是名词,一个实体只描述一件事情,不能重复出现含义相同的实体。

数据库中每个不同的实体都拥有一个与之相对应的表,按照以上的酒店管理系统需求,在酒店管理系统数据库中,会对应至少两张表,分别是客房表和客人表。

3. 标识每个实体需要存储的详细信息

将数据库中的主要实体标识为表的候选实体以后,就要标识每个实体存储的详细信息,也称为该实体的属性,这些属性将组成表中的列。简单的说,就是需要细分出每个实体中包含的子成员信息。

下面以酒店管理系统为例,逐步分解每个实体的子成员信息,如下:

  • 客房:客房号、客房类型、客房状态、客房描述、床位数、入住人数、价格
  • 客人:客人姓名、身份证号、客人编号、入住日期、结账日期、押金、总金额

在进行实体属性分解时,含义相同的成员信息不能重复出现,如联系方式和电话等。

每个实体对应一张表,实体中的每个子成员分别对应表中的每一列。例如,从上述关系可以看出客人应该包含姓名和身份证号等列。

4. 标识实体之间的关系

关系型数据库有一项非常强大的功能,即它能够关联数据库中各个项目的相关信息,不同类型的信息可以单独存储,但是如果需要,数据库引擎还可以根据需要将数据组合起来。

在设计过程中,要标识实体之间的关系,首先需要分析数据库表,确定这些表在逻辑上是如何相关的,然后添加关系列建立起表之间的连接。

以酒店管理系统为例,客人与客房有主从关系,我们需要在客房实体中标明其入住的客房号。

绘制E-R图:DBA和程序猿的必备技能

在《数据库需求分析阶段详解》一节我们讲解了如何解决客户的业务和数据处理需求。需求分析阶段过后,就进入了概要设计阶段。

在概要设计阶段,我们需要和项目团队中的其他成员及客户进行沟通,讨论数据库的设计是否满足客户的业务和数据处理需求。

数据库设计和机械、建筑等行业一样,机械行业需要机械制图,建筑行业需要施工图,而数据库设计也需要图形化的表达方式——E-R图(Entity-Relationship),也可以称为实体-关系图。

实体关系图构成要素

E-R图是设计数据库的工具之一,一般情况下,用于建立数据库的概念模型。实体、属性和关系是构成 E-R图的基本要素。

1. 实体

实体是指现实世界中客观存在并可以相互区分的对象或事物。就数据库而言,实体往往指某类事物的集合。可以是具体的人和事物,也可以是抽象的概念、联系。例如,酒店管理系统中的客房(如 1008 客房、1018 客房等)、客人(如张三、李四、王五)等。

实体一般是名词,对应表中的一行数据。例如,用户张三是一个实体,他对应于客人表中“张三”所在的一行数据,包括客人姓名、身份证号等信息。严格的说,实体用来指表中的一行特定数据。但在开发时,我们也常常把表称为一个实体。

2. 属性

属性可以理解为实体的特征。例如,”客人“这一实体的属性有入住日期、结账日期和交付的押金等。属性用来对应表中的列。

3. 关系

关系是两个或多个实体之间的关联关系。

关系可分为以下 4 种类型,例如对于实体 X 和 Y 之间的关系就必须为以下任意一种:

1)一对一

X 中的一个实体最多与 Y 中的一个实体关联,并且 Y 中的一个实体最多与 X 中的一个实体关联。

比如,每辆汽车同一时刻只能占用一个车位,同一时刻每个车位也只停放一辆汽车,那么,汽车实体与车位实体之间就是一对一的关系。一对一关系也可以表示为 1 : 1。

2)一对多

X 中的一个实体可以与 Y 中任意数量的实体关联,Y 中的一个实体最多与 X 中的一个实体关联。

比如,一间客房可以入住多位客人,但是一位客人只能入住一间客房,所以,客房实体和客人实体之间就是典型的一对多的关系。一对多关系也可以表示为 1 : N。

3)多对一

X 中的一个实体最多与 Y 中的一个实体关联,Y 中的一个实体可以与 X 中的任意数量的实体关联。

比如,客房实体和客人实体之间是典型的一对多关系,反过来说,客人实体和客房实体之间就是多对一的关系。

4)多对多

X 中的一个实体可以与 Y 中的任意数量的实体关联,反之亦然。

比如,图书馆的每本书可以借给多个读者,每个读者也可以借阅多本书,那么,图书实体和读者实体之间就是典型的多对多关系。再如,产品和订单之间也是多对多关系,每个订单中可以包含多个产品,一个产品也可以出现在多个订单中。多对多关系也可以表示为 M : N。

绘制实体关系图

E-R 图以图形的方式来表示数据库的整个逻辑结构,如下:

  • 实体:用矩形表示
  • 属性:用椭圆形表示
  • 关系:用菱形表示
  • 使用直线连接属性和实体,以及连接实体和关系

E-R(实体关系)图的绘制一般用软件辅助,画实体关系图常用的软件有:WORD、亿图图示、Visio 等,PPT 也可以绘制出美观的实体关系图。

下图为客人实体和客房实体之间的联系。

E-R图

在 E-R图中,直线可以有方向(在末端有一个箭头)的来表示实体之间的关系。

下面演示如何绘制不同关系的 E-R 图。其中一些语言可能不符合实际情况,另外,为了方便大家观看,我们省略了实体中的属性,只留下了实体和关系。

1)一对一

一对一E-R图

上图表示为一位客人最多入住一间客房,一间客房最多被一位客人入住。

2)一对多

一对多E-R图

上图表示一位客人可以入住多间客房,一间客房最多被一位客人入住。

3)多对一

多对一E-R图

上图表示一位客人最多入住一间客房,一间客房可以被多位客人入住。

4)多对多

多对多E-R图

上图表示一位客人可以入住多间客房,一间客房也可以被多位客人入住。

为了画图更加方便,也可以用 1 代替箭头,用*代替直线。如多对一还可以如下:

多对一E-R图

绘制 E-R 图后,我们还需要反复与客户进行沟通,让客户提出修改意见,以确认系统中的数据处理需求是否正确完整。

数据库逻辑结构设计阶段(非常重要)

我们在概要设计阶段解决了客户的需求,并绘制了 E-R图。逻辑结构设计的任务,就是把在概要结构设计阶段建立的基本 E-R 图,按选定的关系数据模型的原则转换成相应的数据库模型图。

本节将介绍如何将 E-R图转化为关系模型和数据库模型图,下一节我们介绍如何判断数据表结构设计是否规范。

关系数据库模式

用二维表的形式表示实体与实体间关系的数据模型称为关系模型。关系数据库模式是对关系数据库结构的描述,或者说是对关系数据库框架的描述。一个关系通常对应一张表。

一般情况下,我们把关系模式表示为R(U) 或 R(A,B)。其中,R 表示关系名,U 表示属性集合,A、B 代表 U 中的属性。

将 E-R 图转换为关系模式的步骤如下。

1)把每个实体都转化为关系模式 R(A,B) 形式

以酒店管理系统为例,实体“客人”和“客房”分别可以使用关系模式表示如下。

  • 客房(客房号,客房描述,客房类型,客房状态,床位数,入住人数,价格)。
  • 客人(客人编号,客人姓名,身份证号,入住日期,结账日期,押金,总金额)。

2)建立实体间关系的转换

实体间的关系分成一对一、一对多、多对多三种,当两个实体各自转化为关系模式后,实体间关系的转换如下。

  • 一对一的转换:把任意实体的主键放到另一个实体的关系模式中。
  • 一对多的转换:把关系数量为 1 的实体的主键放到关系数量为 N 的实体关系模式中。
  • 多对多的转换:把两个实体中的主键和关系的属性放到另一个关系模式中,会多生成一个关系模式。

酒店管理系统中客房与客人的关系为一对多关系,转换后的结果如下。

  • 客房(客房号,客房描述,客房类型,客房状态,床位数,入住人数,价格)。
  • 客人(客人编号,客人姓名,身份证号,入住日期,结账日期,押金,总金额,客房号)。

上述关系模式中含有下划线的属性代表主属性,在表中作为主键,加粗属性为外键。

数据库模型图

数据库模型图主要用来说明数据库有哪些表,表中有哪些属性以及表与表之间的关联关系。

将 E-R 图转化为数据库模型图主要执行以下 4 步:

  1. 在 Visio 中新建数据库模型图
  2. 添加实体,将 E-R图中各实体转化为对应的表
  3. 将各属性转化为各表对应的列,定义字段名称、数据类型等
  4. 添加实体之间的映射关系

添加实体之间的映射关系,具体步骤如下:

  • 添加 GuestRecord(客人)实体和 Room(客房)实体。
  • 为 GuestRecord 表添加外键约束列 RoomID(客房号),对应 Room 表中的 RoomID 列。

为了数据编码的兼容性,建议使用英文字段。为了直观可见,我们在英文括号内注明了对应的中文含义。下面将 E-R 图中的“客房”和“客人”两个实体转换为数据库模型图,如下图所示。

图中的 PK 表示表的主键列,FK 表示外键列。需要注意的是,表中的 ID 编号列只能用作主键或外键,否则该列没有实际含义。如客人表中的 GuestID 列,客房表中添加的 RoomID 列。

在数据库模型图中也可以很好的体现实体之间的映射关系。比如,客房和客人之间是一对多关系,对于一对多关系的两个实体,一般会各自转换为一张表,并且后者对应的表引用前者对应的表,即客人(GuestRecord)表中的客房号来自客房(Room)表中的客房号,它们之间应建立主键、外键关系,如上图所示。

一般来说,一对多关系是一个表中的主键对应另一个表中可重复字段,主键的值是不能重复的,而关联的字段是可以重复的,这样就会存在一个值对应一个值或者一个值对应多个值。在一对一关系中,一般是一个主键对应一个不可重复的字段,显然只能一个值对应一个值。

多对多映射关系也是比较常见的。要表示多对多关系,除了将多对多关系中的两个实体各自转换为表外,一般还会创建第三个表,称为连接表。它将多对多关系划分为两个一对多关系,并且将这两个表的主键都插入到第三个表中。

例如,订单表和产品表有多对多关系,这种关系通常通过与“订单明细”表建立两个一对多关系来定义。一个订单可以有多个产品,每个产品可以出现在多个订单中。关于这一点可以在以后的数据库设计实例中慢慢理解。

结合实例,彻底搞懂数据库设计的三大范式

为了建立冗余较小、结构合理的数据库,设计数据库时必须遵循一定的规则。在关系型数据库中,这种规则就是范式。范式是符合某一种级别的关系模式的集合。关系型数据库中的关系必须满足一定的要求,即满足不同的范式。

目前关系型数据库有六种范式,分别为:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、第四范式(4NF)、第五范式(5NF)和第六范式(6NF)。要求最低的范式是第一范式。第二范式在第一范式的基础上又进一步的添加了要求,其余范式依次类推。

一般说来,数据库只需满足第三范式就行了,而通常我们用的最多的就是第一范式、第二范式、第三范式,也就是接下来要讲的“三大范式”。

1)第一范式

第一范式(1NF)用来确保每列的原子性,要求每列(或者每个属性值)都是不可再分的最小数据单元(也称为最小的原子单元)。

例如,客人住宿信息表 (姓名, 客人编号, 地址, 客房号, 客房描述, 客房类型, 客房状态, 床位数, 入住人数, 价格)。

其中,“地址”列还可以细分为国家、省、市、区等,甚至有的程序还把“姓名”列也拆分为“姓”和“名”等。如果业务需求中不需要拆分“地址”和“姓名”列,则该数据表符合第一范式,如果需要将“地址”列拆分,则下列写法符合第一范式:

客人住宿信息表(姓名, 客人编号, 国家, 省, 市, 区, 门牌号, 客房号, 客房描述, 客房类型, 客房状态, 床位数, 入住人数, 价格)。

2)第二范式

第二范式(2NF)在第一范式的基础上更进一层,要求表中的每列都和主键相关,即要求实体的唯一性。如果一个表满足第一范式,并且除了主键以外的其他列全部都依赖于该主键,那么该表满足第二范式。

客人住宿信息表中的数据主要用来描述客人住宿信息,所以该表主键为(客人编号,客房号):

  • “姓名”列、“地址”列➡“客人编号”列。
  • “客房描述”列、 “客房类型”列、“客房状态”列、“床位数”列、“入住人数”列、“价格”列➡“客房号”列。

其中,“➡”符号代表依赖。以上各列没有全部依赖于主键(客人编号,客房号),只是部分依赖于主键,不符合第二范式。

使用第二范式后,客人住宿信息表可以分解成以下两个表:

  • 客人信息表(客人编号,姓名,地址,客房号,入住时间,结账日期,押金,总金额),主键为“客人编号”列,其他列都全部依赖于主键列。
  • 客房信息表(客房号,客房描述,客房类型,客房状态,床位数,入住人数,价格),主键为“客房号”列,其他列都全部依赖于主键列。

3)第三范式

第三范式(3NF)在第二范式的基础上更进一层,第三范式是确保每列都和主键列直接相关,而不是间接相关,即限制列的冗余性。如果一个关系满足第二范式,并且除了主键以外的其他列都依赖于主键列,列和列之间不存在相互依赖关系,则满足第三范式。

为了更好的理解第三范式,这里我们需要了解传递依赖。假设A、B 和 C 是关系 R 的三个属性,如果 A➡B 且 B➡C,则从这些函数依赖中,可以得出 A➡C。如上所述,依赖 A➡C 称之为传递依赖。

以第二范式中的客房信息表为例,初看该表时没有问题,满足第三范式,每列都和主键列“客房号”相关,再细看会发现:

  • "床位数” 列、“价格”列➡“客房类型”列。
  • “客房类型”列➡“客房号”列。
  • “床位数”列、“价格”列➡“客房号”列

为了满足第三范式,应该去掉“床位数”列,“价格”列和“客房类型”列,将客房信息表分解为如下两个表。

  • 客房表(客房号,客房描述,客房类型编号,客房状态,入住人数)
  • 客房类型表(客房类型编号,客房类型名称,床位数,价格)

主键与外键在多表中的重复出现不属于数据冗余,非键字段的重复出现才是数据冗余。在客房表中客房状态存在冗余,需要进行规范化,规范化以后的表如下:

  • 客房表(客房号,客房描述,客房类型编号,客房状态编号,入住人数)。
  • 客房状态表(客房状态编号,客房状态名称)

最后,满足三大范式的 E-R 图如下所示:

满足三大范式的数据库模型图如下所示:

4)反范式化

不满足范式的数据库设计,就是反范式化。

我们需要知道对于项目的最终用户来说,用户关心的是方便,清晰的数据结果。所以在设计数据库时,设计人员和客户在数据库的设计规范化和性能之间会有一定的矛盾。

上面我们通过三大范式将客房表分解出两个表,为了满足客户的需求,最终可能需要通过三个或四个表之间的连接查询,来得到客户需要的数据结果,插入数据同样如此,对于客户输入的数据,我们需要分开插入到三个或四个不同的表中。

由此可以看出,为了满足三大范式,我们的数据操作性能会受到相应的影响。

所以,在实际的数据库设计中,既要考虑三大范式,避免数据的冗余和各种数据操作异常,又要考虑数据访问性能。为了减少表连接,提高数据库的访问性能,也可以允许适当的数据冗余列,这也许就是最合适的数据库设计方案。

比如,有一张存放商品的基本表,数据表中包括“单价”、“数量”“金额”等字段。“金额”这个字段就说明该表的设计不满足第三范式,因为“金额”可以由“单价”乘以“数量”得到,说明“金额”是冗余字段。

与第三范式中介绍的冗余相比,前面介绍的冗余属于低级冗余,我们反对低级冗余,但这里的冗余为高级冗余,目的是提高数据的处理速度,增加“金额”列后,可以提高查询统计的速度,这是以空间换取时间的做法。

注意:不要轻易违反数据库设计的规范化原则,如果处理不好,可能会适得其反,使应用程序运行速度更慢。

优缺点

最后我们来总结一下范式化和反范式化的优缺点。

1)范式化

优点如下:

  • 减少数据冗余
  • 范式化后的表中只有很少的重复数据,更新时只需要更新较少的数据,所以范式化的更新操作比反范式化更快
  • 范式化的表通常比反范式化更小

缺点如下:

  • 范式化的表在查询时经常需要很多的关联,这回导致性能降低
  • 增加了索引优化的难度

2)反范式化

优点如下:

  • 可以减少表的关联
  • 可以更好的进行索引优化

缺点如下:

  • 数据表存在数据冗余及数据维护异常
  • 对数据的修改需要更多的成本

不使用三大范式会对数据库造成什么影响?

我们在《数据库设计的三大范式》一节讲解了三大范式,本节主要介绍不使用三大范式会对设计数据库有什么影响,会出现什么问题。

这里可以将三大范式理解为:设计数据库时需要遵循的规则,可以有效的帮助我们建立冗余小且结构合理的数据库。

在概要设计阶段,同一个项目,10 个设计人员可能设计出 10 种不同的  E-R 图。不同的角度可以标识出不同的实体,实体又包括不同的属性,自然就设计出了不同的 E-R图。那么怎样审核这些设计图呢?如何评审出最优的设计方案呢?答案就藏在后面的内容里。

下面以某酒店的客人住宿信息表为例来介绍,该表用于保存酒店提供住宿的客房信息,如表 1 所示。

表 1 客人住宿信息表

客人编号姓名地址客房号...客房描述客房类型客房状态床位数价格入住人数
C1001张三Addr11001...A栋1层单人间入住1128.001
C1002李四Addr22002...B栋2层标准间入住2158.002
C1003王五Addr32002...B栋2层标准间入住2168.002
.................................
C8006A1Addrm8006...C栋3层总统套房入住31080.003
C8008A2Addrn8008...C栋3层总统套房空闲31080.000

从用户的角度来说,将所有信息放在一个表中很方便,因为这样查询数据库可能会比较容易,但是上述表具有下列问题。

1. 信息重复(冗余)

上表中“客房类型”“客房状态”和“床位数”列中有许多重复的信息,如“标准间”“入住”等。信息重复会造成存储空间的浪费及一些其他的问题。比如,不小心输入了“标准间”和“标间”或“总统套房”和“总统套”,那么它们在数据库中将表示四种不同的客房类型。

2. 更新异常

冗余信息不仅浪费存储空间,还会增加更新的难度。如果需要将“客房类型”修改为“标间”而不是“标准间”,则需要修改所有包含该值的行。如果由于某种原因,没有更新所有行,那么数据库中会出现两种客房类型,一个是“标准间”,另一个是“标间”,这种情况被称为更新异常。

3. 插入异常(无法表示某些信息)

从表 1 中我们会发现 2002 和 2003 客房的居住价格分别是 168 元和 158 元。尽管这两间客房都是标准间类型,但它们的“价格”出现了不同,这样就造成了同一个酒店相同类型的客房价格不同,这种问题被称为插入异常。

4. 删除异常(丢失有用的信息)

在某些情况下,当删除一行时,可能会丢失有用的信息。例如,如果删除客房类型为“1001”的行,就会丢失客房类型为“单人间”的账户的信息,该表只剩下两种客房类型,即“标准间”和“总统套房”。当查询有哪些客房类型时,将会误以为只有“标准间”和“总统套房”两种客房类型,这种情况被称为删除异常。