1 数据库设计概述
数据库设计要求设计人员对数据库有深入的理解,才能设计出高质量的数据库。数据库设计一般分为6个阶段,分别是需求分析、概念数据库设计、逻辑数据库设计、物理数据库设计、数据库实施、数据库运行和维护。
(1)需求分析
在需求分析阶段,数据库设计人员需要分析用户的需求,将分析结果记录下来,形成需求分析报告。在这个阶段中,双方需要进行深入的沟通,以避免理解不准确导致后续的工作出现问题。在需求分析中有许多琐碎、耗时的工作,常见的工作如下。
(1)收集数据。一个企业内的数据可能分散、零碎,由不同人员负责管理。为了使用数据库系统管理这些数据,需要尽可能多地收集数据,并理解企业的业务过程和数据处理流程,理解数据处理的性能需求。可以利用数据流图等工具辅助分析与理解。
(2)解决冲突,包括命名冲突(同名异义、异名同义)属性冲突、结构冲突。例如,商品库存数量是否包含已下订单未出库数量;到货数量和入库数量以哪一个为准;用户名和昵称、真实姓名如何区分;性别使用男、女,还是0、1或f、m来表示。
(3)为数据形成一些标准,如商品编号一共有多少位,未来是否会增加位数,每一位的含义是什么;订单编号按照什么规则生成,如何避免编号重复,编号中包含哪些信息,是否加入一些随机数防止被推测等。
(2)概念数据库设计
在概念数据库设计阶段,将对用户的需求进行综合、归纳、抽象,形成概念模型。概念模型使设计人员摆脱数据库系统的具体技术问题,将精力集中在分析数据及数据之间联系等方面。一般通过绘制E-R图,直观呈现数据库设计人员对用户需求的理解。
(3)逻辑数据库设计
逻辑数据库设计面向数据库系统,在概念数据库设计中完成E-R图等成果后,将其转换为DBMS支持的数据模型(如关系模型),完成实体、属性和联系的转换。
在进行逻辑数据库设计时,应遵循一些规范化理论,如范式(将在后面的小节中详细讲解)。不规范的设计可能会导致数据库出现大量冗余、插入异常、删除异常等问题。
(4)物理数据库设计
物理数据库设计阶段需要确定数据库的存储结构、文件类型等。通常DBMS为了保证其独立性与可移植性,承担了大部分任务,数据库设计人员只需要考虑硬件、操作系统的特性,为数据表选择合适的存储引擎,为字段选择合适的数据类型等,以及评估磁盘空间需求等工作。
(5)数据库实施
数据库实施就是将前面那些工作的成果实施起来,比如使用SQL语句创建数据库、数据表,编写与调试应用程序等。
(6)数据库运行和维护
数据库运行和维护就是将数据库系统正式投入运行,在运行后进行一些维护、调整、备份、升级等工作。
2 数据库设计范式
数据库设计对数据的存储性能、数据的操作都有很大的关系。为了避免不规范的数据库出现数据冗余,造成插入、删除、更新操作异常等情况,就要满足一定的规范化要求,这就是范式(Normal Form)。
根据要求的程度不同,范式有多种级别,最常用的有第一范式(1NF)、第二范式(2NF)和第三范式(3NF),它们由Edgar Frank Codd于1971年相继提出。后来,又有人提出了Boyce-Codd范式(BCNF)、第四范式(4NF)和第五范式(5NF)等。一般来说,数据库设计只需满足第三范式(3NF)就可以了,下面对前三范式进行详细讲解。
(1)第一范式(1NF)
第一范式(1NF)是指数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有多个值,或不能有重复的属性。简而言之,第一范式遵从原子性,属性不可再分。
为了使读者更好地理解,下面通过表1和表2演示不满足第一范式的情况(表格中的列标题“编号”表示主键,用下划线标注)。
表1 用户联系方式表
编号 | 联系方式 |
---|---|
1 | 张三 邮箱:zhangsan@example.com,手机号:18900000000 |
2 | 李四 邮箱:lisi@example.com,手机号:15900000000、17300000000 |
表2 用户联系方式表
编号 | 用户名 | 邮箱 | 手机号 | 手机号 |
---|---|---|---|---|
1 | 张三 | zhangsan@example.com | 18900000000 | |
2 | 李四 | lisi@example.com | 15900000000 | 17300000000 |
表1的问题在于“联系方式”包含了多个值,可以再细分;表2的问题在于“手机号”属性重复。
为了满足第一范式,应将用户和联系方式分成两个表保存,两个表是一对多的联系。具体如表3和表4所示。
表3 用户表
用户编号 | 用户名 |
---|---|
1 | 张三 |
2 | 李四 |
表4 联系方式表
编号 | 用户编号 | 联系方式 | 具体值 |
---|---|---|---|
1 | 1 | 邮箱 | zhangsan@example.com |
2 | 1 | 手机号 | 18900000000 |
3 | 2 | 邮箱 | lisi@example.com |
4 | 2 | 手机号 | 15900000000 |
5 | 2 | 手机号 | 17300000000 |
通过表4-3和表4-4可以看出,无论一个用户有多少个联系方式,都可以使用这两张表来保存。
(2)第二范式(2NF)
第二范式(2NF)是在第一范式的 基础上建立起来的,满足第二范式必须先满足第一范式。第二范式要求实体的属性完全依赖于主键,不能仅依赖主键的一部分(对于复合 主键而言)。简而言之,第二范式遵从唯一性,非 主键字段需完全依赖主键。
为了使读者更好地理解,下面通过表5和表6演示不满足第二范式的情况。
表5 订单表
订单编号 | 订单商品 | 购买件数 | 下单时间 |
---|---|---|---|
1 | 铅笔 | 3 | 2019-01-20 8:30:15 |
2 | 钢笔 | 2 | 2019-01-20 8:30:15 |
3 | 圆珠笔 | 1 | 2019-02-12 9:20:16 |
表6 用户表
用户编号 | 订单编号 | 用户名 | 付款状态 |
---|---|---|---|
1 | 1 | 张三 | 已支付 |
1 | 2 | 张三 | 未支付 |
2 | 3 | 李四 | 已支付 |
在表6中,用户编号和订单编号组成了复合主键,付款状态完全依赖复合主键,而用户名只依赖用户编号。
采用上述方式设计的用户表存在如下问题。
插人异常:如果一个用户没有下过订单,则该用户无法插人;
删除异常:如果删除一个用户所有的订单,则该用户也会被删除;
更新异常:由于用户名冗余,修改一个用户时需要修改多条记录。如果稍有不慎,漏改某些记录,会出现更新异常。
为了满足第二范式,将复合主键移动到订单表中,如表7和表8所示。
表7 用户表
用户编号 | 用户名 |
---|---|
1 | 张三 |
2 | 李四 |
表8 订单表
订单编号 | 用户编号 | 订单商品 | 购买件数 | 下单时间 | 付款状态 |
---|---|---|---|---|---|
1 | 1 | 铅笔 | 3 | 2019-01-20 8:30:15 | 已支付 |
2 | 1 | 钢笔 | 2 | 2019-01-20 8:30:15 | 未支付 |
3 | 2 | 圆珠笔 | 1 | 2019-02-12 9:20:16 | 已支付 |
(3)第三范式(3NF)
第三范式(3NF)是在第二范式的基础上建立起来的,即满足第三范式必须先满足第二范式。第三范式要求一个数据表中每一列数据都和主键直接相关,而不能间接相关。简而言之,第三范式就是非主键字段不能相互依赖。
为了使读者更好地理解,下面通过表9演示不满足第三范式的情况。
表9 用户表
用户编号 | 用户名 | 用户等级 | 享受折扣 |
---|---|---|---|
1 | 张三 | 1 | 0.95 |
2 | 李四 | 1 | 0.95 |
3 | 王五 | 2 | 0.85 |
在表9中,用户享受的折扣与用户等级相关,两者存在依赖关系。采用这种方式设计的用户表存在如下问题。
- 插入异常:新插人用户的等级如果在1.2之外,其享受的折扣无处参考;
- 删除异常:如果删除某个等级下所有的用户,该等级对应的折扣也被删除;
- 更新异常:如果修改某个用户的等级,折扣也必须随之修改;如果修改某个等级的折扣,又因为折扣存在冗余,容易发生漏改。
为了满足第三范式,将等级与折扣拆分到单独的表中,如表10和表11所示。
表10 用户表
用户编号 | 用户名 | 用户等级 |
---|---|---|
1 | 张三 | 1 |
2 | 李四 | 1 |
3 | 王五 | 2 |
表11 折扣表
用户等级 | 享受折扣 |
---|---|
1 | 0.95 |
2 | 0.85 |
多学一招:函数依赖
函数依赖(Functional Dependency)是由数学派生的术语,是数据依赖的一种类型。它表示根据一个属性(或属性集)的值,可以找到另一个属性(或属性集)的值。例如,将商品关系模式中的属性“商品id”设为X,属性集(商品名称,商品价格)设为Y,根据X可以找到Y,说明X决定了Y,Y函数依赖于X,记为X→Y。
函数依赖根据依赖属性的不同可以分为完全函数依赖、部分函数依赖和传递函数依赖,具体如下。
完全函数依赖。以表8为例,使用订单编号和用户编号可以决定付款状态,而若只有订单编号,无法决定是哪一个用户创建了订单,只有用户编号,无法决定是哪一个订单。因此,付款状态这个属性函数依赖(订单编号,用户编号)属性集。
(2)部分函数依赖。以表6为例,用户名依赖用户编号,但不依赖订单编号,因此用户名这个属性部分函数依赖(订单编号,用户编号)属性集。
(3)传递函数依赖。以表9为例,享受折扣依赖用户等级,用户等级依赖用户编号,所以享受折扣传递函数依赖用户编号。
由此可见,在第一范式限定了一个关系模式的所有属性都是不可分的基本数据项以后,第二范式消除了部分函数依赖,第三范式消除了传递函数依赖。
多学一招:反范式
反范式是一种逆规范化设计,其目的主要是为了提高查询效率。范式虽然减少了数据冗余,但是增加了表的数量,这会使查询变得复杂,尤其是连接多张表查询数据时,会使查询性能降低。例如,商品销量可以通过查询订单表中的购买记录计算出来,当需要查询大量商品的销量时,就需要花费许多时间去计算销量。
为了提高查询效率,不必每次查询计算销量,可以违反范式的要求,在商品表中增加一个销量字段,当商品被购买时就增加销量字段。这种方式的缺点是容易出现数据不一致的问题。例如,用户购买商品后,程序出现意外没有增加销量,销量数据就有误了。
在实际开发中,若选择采取反范式的设计,应该提前评估其可能出现的问题,并准备一套解决方案。例如,通过存储过程进行操作,定期检查数据的一致性等。
超全面的测试IT技术课程,0元立即加入学习!有需要的朋友戳:
腾讯课堂测试技术学习地址
欢迎转载,但未经作者同意请保留此段声明,并在文章页面明显位置给出原文链接。