关系型数据库树形结构实现-物化路径模型

一般系统中都会有需求在数据库中存储树形结构。如果系统中用的是非关系型数据库如mongodb,那么没什么好说的。如果系统中用的是关系型数据库,由于关系型数据库天生的表结构,在描述树形结构的时候,不那么直观,在对树进行操作时,并不方便。

通常数据库中描述树形结构采用的是邻接表模型。这种模型,每条记录代表树中的一个节点。每个节点有id字段,和一个关联父节点的字段pid。

对于常用的操作,添加一个叶子节点,删除一个叶子节点,删除一颗子树,移动一个叶子节点,移动一颗子树,查询祖先节点,查询后代节点列表,查询后代节点树,查询整个树、更新某个子树所有节点的某个属性值。邻接表模型在处理他们的时候,难易程度不一,有的实现起来有严重的性能问题。比如查询后代节点树,需要用到递归,要发送多次的请求,与数据库交互多次。一般为了避免递归给数据库发送sql,采用一次性读取整个表的数据,然后在应用端构建树的方法。但是有些情况并不需要整个树,可能需要的是某一个子树,这样每次都把整个树加载出来,性能上并不好。另一种方案是,使用存储过程。但是这样对开发者要求更高,要熟练某个数据库的存储过程。存储过程的开发,并没有好的ide和调试环境,可靠性差。

在关系型数据库中保存树,还有四种解决方案。

一、物化路径模型

二、嵌套集合模型(左右值模型)

三、闭包表模型

四、使用关系型数据库的json类型存储

邻接表模型,每个节点保存的结构信息,是最小化的,只保存了对父节点的位置信息。所以要进行更大范围的操作,就相对麻烦了。

物化路径模型,每个节点保存的结构信息,丰富一些。包括祖先各节点的路径,后代节点路径的一部分,兄弟节点路径的长度,节点所在层次。

嵌套集合模型,相对而言,结构信息的描述,并不是通过各个节点单个描述然后汇总的,而是先从全局规划。

闭包表模型,以空间换时间,牺牲了太多的空间,而且维护起来比较麻烦。

从性能、可靠性(容易实现,不容易出bug)、可维护性(简单、不需要依赖大量额外的处理逻辑)角度考虑,物化路径模型是一个相对好的选择。


这里实现了一个物化路径模型的树 https://github.com/zhoujiaping/path-test

物化路径模型,主要的缺点以及难点在于,不能实现无线深度的树,每个节点的子节点数有上限,而且深度越大,路径越长,还要解决操作过程中节点路径的唯一性。

我们采用4个字符描述一个子节点。其中一个分隔符(非必须,为了可读性加上去的),另外三个用来指定唯一路径。

三个字符,可以转成36进制的数值。这样每个节点下最多有36*36*36(46656)个子节点(linux操作系统ext3文件系统一级子目录的个数上限默认为31998个)。在一般的应用中足够了。(为什么用36进制,因为进制越大,相同个数的字符能表示的数值范围越大,java中Integer.toString(num,redix)方法,最大的进制是36。

当树的层数达到10层的时候,路径的长度=4*10=40。这非常容易接受(UUID有36个字符)。

路径唯一性

这在插入节点,移动节点时要考虑。插入节点时,先获取所有兄弟节点,然后将路径从000到ZZZ遍历,如果在兄弟节点中找不到,那么就找到了一个未使用过的路径,遍历结束。移动节点时,也要先获取所有兄弟节点,然后和上面不同的是,这次要取多个未使用过的路径。然后不仅要更新路径中的祖先部分,还要更新路径在当前目录下的部分。当然这些都是可以在应用端实现的。最后,如果发现这个模型不适合当前场景,还可以将它进行转换,用其他模型替换(模型之间是可以相互转换的)。

猜你喜欢

转载自blog.csdn.net/zhoujiaping123/article/details/74090571