九)规范化——范式
当笔者还在大学时,范式是学习关系型数据库时最头疼的问题。我想也许会有读者仍然不理解范式的价值,简单来说——范式将帮助我们来保证数据的有效性和完整性。规范化的目的如下:
- 消灭重复数据。
- 避免编写不必要的,用来使重复数据同步的代码。
- 保持表的瘦身,以及减从一张表中读取数据时需要进行的读操作数量。
- 最大化聚集索引的使用,从而可以进行更优化的数据访问和联结。
- 减少每张表使用的索引数量,因为维护索引的成本很高。
规范化旨在——挑出复杂的实体,从中抽取出简单的实体。这个过程一直持续下去,直到数据库中每个表都只代表一件事物,并且表中每个描述的都是这件事物为止。
1 规范化实体和属性去除冗余)
1NF:每个属性都只应表示一个单一的值,而非多个值。
需要考虑几点:
- 属性是原子性的 需要考虑熟悉是否分解的足够彻底,使得每个属性都表示一个单一的值。和“三)列意味着唯一的值”描述的原则相同。)分解原则为——当你需要分开处理每个部分时才分解值,并且分解到足够用就行。即使当前不需要彻底分解属性,也应该考虑未来可能的需求变更。)
- 属性的所有实例必须包含相同数量的值 实体有固定数量的属性表有固定数量的列)。设计实体时,要让每个属性只有固定数量的值与其相关联。
- 实体中出现的所有实体类型都必须不同
当前设计不符合1NF的“臭味”:
- 包含分隔符类字符的字符串数据。
- 名字尾端有数字的属性。
- 没有定义键或键定义不好的表。
2 属性间的关系去除冗余)
2NF-实体必须符合1NF,每个属性描述的东西都必须针对整个键可以理解为oop中类型属性的内聚性)。
当前设计不符合2NF的“臭味”:
- 重复的键属性名字前缀设计之外的数据冗余) 表明这些值可能描述了某些额外的实体。
- 有重复的数据组设计之外的数据冗余) 这标志着属性间有函数依赖型。
- 没有外键的复合主键 这标志着键中的键值可能标识了多种事物,而不是一种事物。
3NF-实体必须符合2NF,非键属性不能描述其他非键属性。与2NF不同,3NF处理的是非键属性和非键属性之间的关系,而不是和键属性之间的关系。
当前设计不符合3NF的“臭味”:
- 多个属性有同样的前缀。
- 重复的数据组。
- 汇总的数据,所引用的数据在一个完全不同的实体中。有些人倾向于使用视图,我更倾向于使用对象集合,即由程序来完成。)
BCNF-实体满足第一范式,所有属性完全依赖于某个键,如果所有的判定都是一个键,则实体满足BCNF。BCNF简单地扩展了以前的范式,它说的是:一个实体可能有若干个键,所有属性都必须依赖于这些键中的一个,也可以理解为“每个键必须唯一标识实体,每个非键熟悉必须描述实体。”
3 去除实体组合键中的冗余
4NF-实体必须满足BCNF,在一个属性与实体的键之间,多值依赖一条记录在整个表的唯一性由多个值组合起来决定的)不能超过一个。
当前设计不符合4NF的“臭味”:
- 三元关系实体:实体:实体)。
- 潜伏的多值属性。如多个手机号。)
- 临时数据或历史值。需要将历史数据的主体提出,否则将存在大量冗余。)
4 尽量将所有关系分解为二元关系
5NF-实体必须满足4NF,当分解的信息无损的时候,确保所有关系都被分解为二元关系。
5NF保证在第四范式中存在的任何可以分解为实体的三元关系都被分解。有的三元关系可以在不丢失信息的前提下被分解为二元关系,当分解为两个二元关系的过程要丢失信息时,关系被宣称为处于第四范式中。所以,第五范式建议是,最好把现有的三元关系都分解为3个二元关系。
需要注意的是,规范化的结果可能是更多的表,更复杂的查询。因此,处理到何种程度,取决于性能和数据架构的多方考量。建议规范化到第四范式,原因是5NF的判断太过隐晦。例如:表X老师,学生,课程)是一个三元关系,可以分解为表A老师,学生),表B学生,课程),表C老师,课程)。表X表示某个老师是上某个学生的某个课程的老师;表A表示老师教学生;表B表示学生上课;表C表示老师教课。单独看是无法发现问题的,但是从数据出发,"表X=表A+表B+表C"并不一定成立,即不能通过连接构建分解前的数据。因为可能有多种组合,丧失了表X反馈出的业务规则。这种现象,容易在设计阶段被忽略,但好在在开放阶段会被显现,而且并不经常发生。
推荐做法:
- 尽可能地遵守上述规范化原则。
- 所有属性描述的都应该是体现被建模实体的本质的内容。
- 至少必须有一个键,它唯一地标识和描述了所建实体的本质。
- 主键要谨慎选择。
- 在逻辑阶段能做多少规范化就做多少性能不是逻辑阶段考虑的范畴)。
十)选择数据类型MS SQL 2008)
MS SQL的常用类型:
| 精确数字 | 不会发生精度损失 | bit tinyint smallint int bigint decimal |
| 近似数字 | 对于极值可能发生精度损失 | float(N) real |
| 日期和时间 | date time smalldatetime datetime datetime2 datetimeoffset | |
| 二进制数据 | bingary(N) varbinary(N) varbinary(max) | |
| 字符串)数据 | char(N) varchar(N) varchar(max) nchar(N) nvarchar(N) nvarchar(max) | |
| 存储任意数据 | sql_variant | |
| 时间戳 | timestamp | |
| GUID | uniqueidentifier | |
| XML | 不要试图使用该类型规避1NF | xml |
| 空间数据 | geometry geography | |
| 层次数据 | heirarchyid |
MS SQL中不在支持的或糟糕的类型选择
- image:被varbinary(max)取代。
- text和ntext:被varchar(max)和nvarchar(max)取代。
- money和smallmoney:开发过程中不好用,建议使用decimal。
常用类型选择:
类型选择的最基本规则是选择满足需要的最轻的类型,因为这样查询更快。
| bool | 建议使用bit而非char(1),因为开发语言对其支持觉好,可以直接映射为bool或bool?。 |
| 大值数据 | 使用所有备选类型中最小的那种,类型越大,查询越慢,当字节大于8000时,应使用max。 |
| 主键 | 自增主键根据预期范围选择int或bigint,GUID使用uniqueidentifier而非varchar(N)。 |
十一)优化并行
设计DB时就应该考虑到对并行进行优化,比如,MS SQL中的timestamp类型就是极好的选择。
四 命名规则
- 表——“模块名_表名”。表名最好不要用复数,原因是在使用ORM框架开发时,代码生成器根据DB生成类定义,表生成了某个实例的类型定义,而不是实例集合。表名不要太长。原因之一,某些软件对表名最大长度有限制;原因之二,使用代码生成器往往会根据表名生产类型名称,之后懒人会直接使用这一名称,如果将太长的名称跨网络边界显然不是明智之举。
- 字段——bool类型用“Is”、“Can”、“Has”等表示;日期类型命名必须包含“Date”;时间类型必须包含“Time”。
- 存储过程——使用“proc_”前缀。
- 视图——使用“view_”前缀。
- 触发器——使用“trig_”前缀。