数据库模型设计连载(7~9)

发表于:2007-04-28来源:作者:点击数: 标签:数据库模型设计连载
连载之7 原创:胖子刘(转载请注明作者和出处,谢谢) 数据库 物理模型设计的其他模式 除了上面提到的四种主要设计模式,还有一些其他模式,在某些项目中可能会用到,在这里先简单做个说明,暂不做深入讨论,等到以后的项目用到这些模式的时候,再结合实际

连载之7
原创:胖子刘(转载请注明作者和出处,谢谢)

数据库物理模型设计的其他模式
除了上面提到的四种主要设计模式,还有一些其他模式,在某些项目中可能会用到,在这里先简单做个说明,暂不做深入讨论,等到以后的项目用到这些模式的时候,再结合实际需求详细解说。
(一)继承模式
继承模式,可以看作是“主从模式”的一种特殊情况(或者说是“变形”),它所代表的两个对象也是“一对多”的关系。它与“主从模式”的区别是,“继承模式”中从表的主键是复合主键,并且复合主键中必定包含主表的主键列。
根据从表继承主表的列的数量,继承模式又分以下两种情况:
1.       从表继承主表的全部列
图7
在这种情况下,从表除了代表自身的专用字段以外,还冗余了主表的全部字段。这种设计方式的缺点显而易见:
  • 数据冗余度大
  •  一致性差
  • 磁盘存储量大
它的优点也显而易见:
  • 正因为它的冗余度大、所以它不易丢失数据。假设主表数据丢失、或者被误操作删改,也能依据从表数据重新生成主表数据;这种设计方式,可以在发生数据损坏的时候从应用的角度进行一定程度的数据恢复,等于是在SQL Server数据库级别的数据恢复功能之上又加了一道保险。
  • 正因为它一致性差、主表数据被重复存储,所以可依据外键关系进行数据验证。将主从表记录作关联比较,如果数据不一致,就可以得知数据要么被人为改动,或者要么程序代码中存在bug
  • 尽管磁盘存储量大,但是数据在查询统计的时候,只需针对从表进行搜索即可,无需关联操作,可以加快检索的速度。这就是数据库模型设计中经常提到的“以空间换时间”。
2.       从表只继承主表的主键列
图8
这种设计方式,从表只继承了主表的主键列,这种方式的优缺点与前面刚好相反。
优点:
  • 数据冗余度小
  • 一致性高
  • 磁盘存储量小
缺点:
  • 正因为它的冗余度小、所以它易丢失数据。假设主表数据丢失、或者被误操作删改,就只能通过SQL Server数据库级别的数据恢复操作来找回丢失的数据了。
  • 正因为它一致性高,所以无法进行应用程序级的数据验证。
  • 由于采用了一致性设计,磁盘存储量较小,但是数据在查询统计的时候,必须要对两个表进行内连接(INNER JOIN)操作,才能搜索到相关数据。而内连接操作时需要耗费一定的时间的。这就是数据库模型设计中经常提到的“以时间换空间”。
当然,在实际的数据库模型设计过程中,还会有介于上述两者之间的第3种情况出现,那就是从表继承了主表的主键列以及部分其他列。这就要求我们设计人员要依据实际的业务需求进行综合分析、权衡、折中,给出最符合业务需求的设计结果。
 
连载之8
原创:胖子刘(转载请注明作者和出处,谢谢)
(二)自联结模式
自联结模式,也可以看作是“主从模式”的一种特殊情况(或者说是“变形”),它在一张表内实现了“一对多关系”,并且可以根据业务需要实现“有限层”或者“无限层”的主从嵌套。
这种模式用得最多的情况就是实现“树形结构”数据的存储,比如各大网站上常见的细分类别、应用系统的组织结构、Web系统的菜单树等都能用到这种模式。
自联结模式有很多变体,且每种变体的优缺点同样鲜明。由于本连载的重点在于对跨行业通用数据库模型设计进行分析,所以对每种具体模式的细节方面的设计技巧不能作详细论述,请大家原谅。这里仅举两个例子说明:
 
1.       简单自联结
简单自联结,就是在一个表里设置当前类ID、父类ID,同时规定最顶层类的父类ID为一个固定值(比如0),在生成树的时候使用递归算法,记录的前后顺序通过“排序号”字段来确定。
图9
这个表用来存储菜单树很方便。首先会有一个主菜单,主菜单下有子菜单,子菜单下面又有孙菜单……菜单的数量不确定、层级不确定,用户可以在任意菜单下增加新的子菜单,或者删除某个子菜单及其下的所有孙菜单……这种设计方式很多人都会用到,短小精悍、维护方便、且完全满足用户需求,而且树的层次不限,扩展起来非常容易。这些都是它的优点。
它的缺点就是树结构的生成由于使用了递归算法,必然要对该表进行多次读取(读取的次数 = 表内的记录数 – 最深层级的记录数),多次读取就来了比较低的运行效率,当表里的记录很多的时候,这个缺点可以称得上是致命的。
于是就有了下面的这种设计模式。
2.       扩展自联结
扩展自联结,与简单自联结的最大区别就是通过附加冗余字段来避免递归运算,所要实现的主要目标就是一次读取就能生成整个树,一次提高树的生成效率。
但是,鱼与熊掌不可兼得,凡事都有两面性。
生成树的效率提高了,增删改表内记录的算法就会相应复杂,并且树的层数也变为有限的了。
所以在此类设计的时候,大家还是要认真分析业务需求,看看实际业务的重点在什么地方,然后再作具体设计。比如一些门户网站在首页显示产品类别是业务重点,那么我们在设计的时候就要尽可能的提高生成树的效率,采取扩展自联结模式;相反,一些基于Web的业务系统,要求对菜单树的增删改维护操作尽量简单,由于菜单的数目不多,所以菜单树的生成效率不是瓶颈,那么我们设计的时候就可以采取简单自联结模式。
关于附加冗余字段实现扩展自联结的方法很多,网上也有很多这方面的帖子,大家可以到Google上搜一下。
在这里仅举一个例子如下:
图10
这个设计与前面的设计最大的区别就是排序字段,前面的简单自联结用了一个整数型的字段来实现排序,这里用了一个Varchar20型的字段“层级代码”来实现大排序。这个字段的取值两位一组,代表一层,假定最深为5层,初始值为0000000000。
按照这样的设计,表内的数据记录可能就是这样的:
ID           TypeName           ParentID            TypeLevel
1             根类别               0                 000000
2             类别1                1                 010000
3             类别1.1              2                 010100
4             类别1.2              2                 010200
5             类别2                1                 020000
6             类别2.1              5                 020100
7             类别3                1                 030000
8             类别3.1              7                 030100
9             类别3.2              7                 030200
10            类别1.1.1            3                 010101
……
现在按TypeLevel字段进行排序,执行如下SQL语句:SELECT * FROM TMP_Type ORDER BY TypeLevel
列出记录集如下:
ID           TypeName           ParentID            TypeLevel
1             总类别               0                 000000
2             类别1                1                 010000
3             类别1.1              2                 010100
10            类别1.1.1            3                 010101
4             类别1.2              2                 010200
5             类别2                1                 020000
6             类别2.1              5                 020100
7             类别3                1                 030000
8             类别3.1              7                 030100
9             类别3.2              7                 030200
……
在控制显示类别的层次时,只要对“层级代码”字段中的数值进行判断,每2位一组,如大于0则向右移2个空格。
连载之9
原创:胖子刘(转载请注明作者和出处,谢谢)
(三)单表模式
单表模式,就是把相关子类的属性统统集中在一个表里,通过“类别”字段来区分表内记录所属的子类以及该类的有效属性。在实际案例当中,单表模式的应用还是很广泛的。举个例子,有车的朋友现在拿出你们的《中华人民共和国机动车行驶证》,翻到“副页”,看看副页登记的档案指标。下图为推测设计图、不代表真实设计。
图11
我是2006年2月份买的车,我的机动车行驶证副页记载了如下档案指标:“号牌号码、车辆类型、总质量、整备质量、核定载质量、准牵引总质量、核定载客、驾驶室共乘、货箱内部尺寸、后轴钢板弹簧片数、外廓尺寸、检验记录。”浏览本文的朋友可以跟自己的行驶证对照一下,尤其是老司机,看看若干年前发的行驶证和现在的有没有区别。
在这里大家可以很清楚的看到,上述指标中“号牌号码、车辆类型、总质量、整备质量、外廓尺寸、检验记录”是各种类型的车辆的公共属性,“核定载质量、准牵引总质量、驾驶室共乘、货箱内部尺寸、后轴钢板弹簧片数”是货运车辆的专有属性,“核定载客”是客运车辆的专有属性。
根据经验我推测,就此种表现形式而言,公安机关交通管理部门的计算机系统应该就是采用“单表模式”进行的设计。通过这一个表就可以容纳包括货车、客车、轿车、摩托车、农用车等所有类型的车辆档案。这里面有一个“车辆类型”属性,这个属性就是用来区分当前记录所属类别,程序代码根据这个属性的值来确定当前档案记录的哪些属性是有效、且需要记录和打印的,哪些属性对当前记录来说是无效的。比如我行驶证上的车辆类型是“轿车”,除公共属性以外,只有“核定载客”指标打印了一个“5人”字样,其余指标打印的都是“--”字样,因为这些指标都是货车才有的,对轿车而言是无意义的。
这种设计有一个明显的好处——如果事先对车辆档案都有哪些指标调研的很充分,且后期基本上不需要扩展,那么系统运行时无论遇上什么类型的车辆档案都不需要变更程序,具有很强的通用性。很明显,行驶证是套打的,这种设计便于大批量的制作证件底单。因为不管什么类型的车辆档案都是这个格式,只要开动机器印刷便是。
凡事有利就有弊,这种设计的弊端也很明显。首先是给人的印象不是很人性化。我明明买的是轿车,你发给我的证件搞那么多货车的指标在上面干嘛?浪费版面!其次,如果一旦后期需要对某种类型的车辆档案扩充属性(无论你前期的需求调研多么充分,都不能保证以后不会变化),假设国家新颁布一部法规或者国标,要求必须在行驶证上记载客车的“发动机排量”、其他类型的车辆不作要求,那么按照这种设计,所有车辆(包括“无辜的”货车在内)都要换证(呵呵,不知道这种换证收不收工本费)!
声明:以上例子仅仅是我的推测,用以说明“单表模式”。我没有交通管理部门的工作经历,如果与实际情况不符,欢迎同行批评指正。
 

原文转自:http://www.ltesting.net