数据库经典设计案例
数据库经典设计案例
说到数据库,我认为不能不先谈数据结构。1996年,在我初⼊⼤学学习计算机编程时,当时的⽼师就告诉我们说:计算机程序=数据结构+算法。尽管现在的程序开发已由⾯向过程为主逐步过渡到⾯向对象为主,但我还是深深赞同8年前⽼师的告诉我们的公式:计算机程序=数据结构+算法。⾯向对象的程序开发,要做的第⼀件事就是,先分析整个程序中需处理的数据,从中提取出抽象模板,以这个抽象模板设计类,再在其中逐步添加处理其数据的函数(即算法),最后,再给类中的数据成员和函数划分访问权限,从⽽实现封装。
数据库的最初雏形据说源⾃美国⼀个奶⽜场的记账薄(纸质的,由此可见,数据库并不⼀定是存储在电脑⾥的数据^_^),⾥⾯记录的是该奶⽜场的收⽀账⽬,程序员在将其整理、录⼊到电脑中时从中受到启发。当按照规定好的数据结构所采集到的数据量⼤到⼀定程度后,出于程序执⾏效率的考虑,程序员将其中的检索、更新维护等功能分离出来,做成单独调⽤的模块,这个模块后来就慢慢发展、演变成现在我们所接触到的数据库管理系统(DBMS)——程序开发中的⼀个重要分⽀。
下⾯进⼊正题,⾸先按我个⼈所接触过的程序给数据库设计⼈员的功底分⼀下类:
1、没有系统学习过数据结构的程序员。这类程序员的作品往往只是他们的即兴玩具,他们往往习惯只设计有限的⼏个表,实现某类功能的数据全部塞在⼀个表中,各表之间⼏乎毫⽆关联。⽹上不少的免费管理软件都是这样的东西,当程序功能有限,数据量不多的时候,其程序运⾏起来没有什么问题,但是如果⽤其管理⽐较重要的数据,风险性⾮常⼤。
2、系统学习过数据结构,但是还没有开发过对程序效率要求⽐较⾼的管理软件的程序员。这类⼈多半刚从学校毕业不久,他们在设计数据库表结构时,严格按照教科书上的规定,死扣E-R图和3NF(别灰⼼,所有的数据库设计⾼⼿都是从这⼀步开始的)。他们的作品,对于⼀般的access型轻量级的管理软件,已经够⽤。但是⼀旦该系统需要添加新功能,原有的数据库表差不多得进⾏⼤换⾎。
3、第⼆类程序员,在经历过数次程序效率的提升,以及功能升级的折腾后,终于升级成为数据库设计的⽼鸟,第⼀类程序员眼中的⾼⼈。这类程序员可以胜任⼆⼗个表以上的中型商业数据管理系统的开发⼯作。他们知道该在什么样的情况下保留⼀定的冗余数据来提⾼程序效率,⽽且其设计的数据库可拓展性较好,当⽤户需要添加新功能时,原有数据库表只需做少量修改即可。
4、在经历过上⼗个类似数据库管理软件的重复设计后,第三类程序员中坚持下来没有转⾏,⽽是希望从中找出“偷懒”窍门的有⼼⼈会慢慢觉悟,从⽽完成量变到质变的转换。他们所设计的数据库表结构有⼀定的远见,能够预测到未来功能升级所需要的数据,从⽽预先留下伏笔。这类程序员⽬前⼤多晋级成数据挖掘⽅⾯的⾼级软件开发⼈员。
5、第三类程序员或第四类程序员,在对现有的各家数据库管理系统的原理和开发都有⼀定的钻研后,要么在其基础上进⾏⼆次开发,要么⾃⾏开发⼀套有⾃主版权的通⽤数据库管理系统。
我个⼈正处于第三类的末期,所以下⾯所列出的⼀些设计技巧只适合第⼆类和部分第三类数据库设计⼈员。同时,由于我很少碰到有兴趣在这⽅⾯深钻下去的同⾏,所以⽂中难免出现错误和遗漏,在此先⾏声明,欢迎⼤家指正,不要藏私哦8)
⼀、树型关系的数据表
不少程序员在进⾏数据库设计的时候都遇到过树型关系的数据,例如常见的类别表,即⼀个⼤类,下⾯有若⼲个⼦类,某些⼦类⼜有⼦类这样的情况。当类别不确定,⽤户希望可以在任意类别下添加新的⼦类,或者删除某个类别和其下的所有⼦类,⽽且预计以后其数量会逐步增长,此时我们就会考虑⽤⼀个数据表来保存这些数据。按照教科书上的教导,第⼆类程序员⼤概会设计出类似这样的数据表结构:
类别表_1(Type_table_1)
名称 类型 约束条件 说明
type_id int ⽆重复类别标识,主键
type_name char(50) 不允许为空类型名称,不允许重复
type_father int 不允许为空该类别的⽗类别标识,如果是顶节点的话设定为某个唯⼀值
这样的设计短⼩精悍,完全满⾜3NF,⽽且可以满⾜⽤户的所有要求。是不是这样就⾏呢?答案是NO!Why?
我们来估计⼀下⽤户希望如何罗列出这个表的数据的。对⽤户⽽⾔,他当然期望按他所设定的层次关系⼀次罗列出所有的类别,例如这样:总类别
类别1
类别1.1
类别1.1.1
类别1.2
类别2
类别2.1
类别3
类别3.1
类别3.2
……
看看为了实现这样的列表显⽰(树的先序遍历),要对上⾯的表进⾏多少次检索?注意,尽管类别1.1.1可能是在类别3.2之后添加的记录,答案仍然是N次。这样的效率对于少量的数据没什么影响,但是⽇后类型扩充到数⼗条甚⾄上百条记录后,单单列⼀次类型就要检索数⼗次该表,整个程序的运⾏效率就不敢恭维了。或许第⼆类程序员会说,那我再建⼀个临时数组或临时表,专门保存类型表的先序遍历结果,这样只在第⼀次运⾏时检索数⼗次,再次罗列所有的类型关系时就直接读那个临时数组或临时表就⾏了。其实,⽤不着再去分配⼀块新的内存来


