在数据库设计中,我们经常遇到这样的问题,如何在关系数据库中保存类似组织机构树或者评论树这样的数据。
通常我们看到的做法是在实体属性之外添加一个parent_id字段,引用上级组织机构或者其它回复。我们可以再建一个外键约束来稳妥一点维护这种关系。

这样的设计叫做邻接表。
使用邻接表时,查询成了大麻烦,如图所示。

可以先查询出所有的行,在应用程序中重新构造这棵树,然后再使用这些数据。但是这样性能显然捉急:每次查询都要查询全表数据并建树。可以使用缓存来优化这样的问题,但是同时造成了缓存一致性问题,并且如果是分布式缓存并且树的数据量很大(假如10M),序列化开销也很大。
其它树模型-1:路径枚举。

| 用这种方案查询祖先:SELECT * FROM Comments AS c WHERE ‘1/4/6/7/’ LIKE c.path | ‘%’; |
| 用这种方案查询后代:SELECT * FROM Comments AS c WHERE c.path LIKE ‘1/4/’ | ‘%’; |
插入数据:你所需要做的只是复制一份要插入节点的逻辑上的父亲节点的路径,并将这个新节点的ID追加到路径末尾就行了。
其它树模型-2:嵌套集

这种设计删除方便,但是其它基本操作很烦,要谨慎使用。
其它树模模型-3:闭包表

例如要获取评论#4的后代,只需要在闭包表中搜索祖先是评论#4的行就可以了。要获取评论#6的所有祖先,只需要在闭包表中搜索后代为评论#6的行就可以了。
各种层级数据比较

邻接表是最方便的设计,并且很多软件开发者都了解它。
如果你使用的数据库支持WITH或者CONNECT BY PRIOR的递归查询,那能使得邻接表的查询更为高效。
枚举路径能够很直观地展示出祖先到后代之间的路径,但同时由于它不能确保引用完整性,使得这个设计非常地脆弱。枚举路径也使得数据的存储变得比较冗余。
嵌套集是一个聪明的解决方案——但可能过于聪明了,它不能确保引用完整性。最好在一个查询性能要求很高而对其他需求要求一般的场合来使用它。
闭包表是最通用的设计,并且本章所描述的设计中只有它能允许一个节点属于多棵树。它要求一张额外的表来存储关系,使用空间换时间的方案减少操作过程中由冗余的计算所造成的消耗。