您的位置:首页 > 数据库

菜单,目录设计--自联结模式--数据库设计

2006-08-18 14:40 344 查看
连载之8
原创:胖子刘(转载请注明作者和出处,谢谢)
(二)自联结模式
自联结模式,也可以看作是“主从模式”的一种特殊情况(或者说是“变形”),它在一张表内实现了“一对多关系”,并且可以根据业务需要实现“有限层”或者“无限层”的主从嵌套。
这种模式用得最多的情况就是实现“树形结构”数据的存储,比如各大网站上常见的细分类别、应用系统的组织结构、Web系统的菜单树等都能用到这种模式。
自联结模式有很多变体,且每种变体的优缺点同样鲜明。由于本连载的重点在于对跨行业通用数据库模型设计进行分析,所以对每种具体模式的细节方面的设计技巧不能作详细论述,请大家原谅。这里仅举两个例子说明:
 
1.       简单自联结
简单自联结,就是在一个表里设置当前类ID、父类ID,同时规定最顶层类的父类ID为一个固定值(比如0),在生成树的时候使用递归算法,记录的前后顺序通过“排序号”字段来确定。



这个表用来存储菜单树很方便。首先会有一个主菜单,主菜单下有子菜单,子菜单下面又有 孙菜单……菜单的数量不确定、层级不确定,用户可以在任意菜单下增加新的子菜单,或者删除某个子菜单及其下的所有孙菜单……这种设计方式很多人都会用到, 短小精悍、维护方便、且完全满足用户需求,而且树的层次不限,扩展起来非常容易。这些都是它的优点。
它的缺点就是树结构的生成由于使用了递归算法,必然要对该表进行多次读取(读取的次数 = 表内的记录数 – 最深层级的记录数),多次读取就来了比较低的运行效率,当表里的记录很多的时候,这个缺点可以称得上是致命的。
于是就有了下面的这种设计模式。
2.       扩展自联结
扩展自联结,与简单自联结的最大区别就是通过附加冗余字段来避免递归运算,所要实现的主要目标就是一次读取就能生成整个树,一次提高树的生成效率。
但是,鱼与熊掌不可兼得,凡事都有两面性。
生成树的效率提高了,增删改表内记录的算法就会相应复杂,并且树的层数也变为有限的了。
所以在此类设计的时候,大家还是要认真分析业务需求,看看实际业务的重点在什么地方, 然后再作具体设计。比如一些门户网站在首页显示产品类别是业务重点,那么我们在设计的时候就要尽可能的提高生成树的效率,采取扩展自联结模式;相反,一些 基于Web的业务系统,要求对菜单树的增删改维护操作尽量简单,由于菜单的数目不多,所以菜单树的生成效率不是瓶颈,那么我们设计的时候就可以采取简单自 联结模式。
关于附加冗余字段实现扩展自联结的方法很多,网上也有很多这方面的帖子,大家可以到Google上搜一下。
在这里仅举一个例子如下:



这个设计与前面的设计最大的区别就是排序字段,前面的简单自联结用了一个整数型的字段来实现排序,这里用了一个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个空格。

 

关于树形结构的速度方面,提点建议:
1.在树形结构中,一般的设计是用一个表纪录其本身的属性,然后用一个字段Parent来记录其父亲是谁,这种做法在树形结构纪录数不多的时候是 可以的,但是当纪录很多的时候,就带来了搜索方面的致命缺点。为什么这样说呢,因为在这个表中只是纪录了父亲是谁,而没有记录孩子有哪些,这样当要搜索一 个节点的所有子,和子的子....的时候,就必然要问表中的所有记录,谁的父亲是他,当找到这个纪录的所有子后,又要记录问同样一个问题,谁的父亲是刚找 出来的"子",这个问题一直第归地问完表中的所有记录。这样必然造成了搜索方面的速度慢。
2.在第一点的设计中,是将父的关系线藏在表中用一个字段Parent来代替,如果我们考虑将关系线抽离出来,专门用来记录父有哪些子。这样在 往下搜索所有子的时候就增加了命中的机会,就不用整个表都问,只需要问这样一个问题:我的孩子有哪些,而不是在问谁的父亲是我。这样就加快了往下搜索所有 子的速度了。
3.在第一点的设计中,在问某纪录上面有多少层的速度是很快的。
4.综上所述,为了搜索方面的速度。我觉得应该是这样的设计:在主表中继续保留Parent字段(方面往上搜索),然后将关系线抽出来,用于记录父有哪些字(方面往下搜索)。
主表:
ID Name Parent
1 A -
2 B 1
3 C 1

关系表
ID Parent Child
1 1 2
2 1 3

coollangzi 发表于2006-05-11 09:00:00  IP: 218.24.136.*
必然冗余表的设计会出现数据不一致的情况,而且这个未必有级链更新的可能,在自连接的表上不难建立两个以上的级链操作 是不是有这样的要求?
所以模型用就用经典的,要不就表上直接做一个2,3树的关系表,在内容表上不做任何关系,甚至做个B+树的关系表,当然也可以把关系表跟内容表合 在一起,这样效率明显会高很多,但是实际上数据库也就是靠内部自己的B树实现这些的,我们再做一次还不如能利用上数据库自己的内部机制.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息