【问题标题】:Managing hierarchies in SQL: MPTT/nested sets vs adjacency lists vs storing paths在 SQL 中管理层次结构:MPTT/嵌套集 vs 邻接列表 vs 存储路径
【发布时间】:2012-01-02 00:41:40
【问题描述】:

一段时间以来,我一直在思考如何最好地处理 SQL 中的层次结构。由于邻接列表的局限性和 MPTT/嵌套集的复杂性,我开始考虑简单地将关键路径存储为简单的node_key/node_key/... 字符串。我决定整理一下这三种技术的优缺点:

创建/删除/移动节点所需的调用次数:

  • 邻接 = 1
  • MPTT = 3
  • Path = 1(将旧节点路径替换为包含该路径的所有节点的新节点路径)

获取树所需的调用次数:

  • 邻接 = [子级数]
  • MPTT = 1
  • 路径 = 1

获取节点/祖先路径所需的调用次数:

  • 邻接 = [超层数]
  • MPTT = 1
  • 路径 = 0

获取子节点数所需的调用次数:

  • 邻接 = [子级数]
  • MPTT = 0(可以从右/左值计算)
  • 路径 = 1

获取节点深度所需的调用次数:

  • 邻接 = [超层数]
  • MPTT = 1
  • 路径 = 0

需要的数据库字段:

  • 邻接 = 1(父)
  • MPTT = 3(父、右、左)
  • 路径 = 1(路径)

结论

存储路径技术在除一个以外的每个用例中都使用与其他技术相同或更少的调用。通过这种分析,存储路径显然是赢家。更不用说,它实现起来要简单得多,易于阅读等。

所以问题是,存储路径不应该被视为比 MPTT 更强大的技术吗?为什么存储路径不是一种更常用的技术,为什么不在给定实例中通过 MPTT 使用它们?

另外,如果您认为此分析不完整,请告诉我。

更新:

以下是 MPTT 开箱即用的至少 2 件存储路径解决方案无法做到的事情:

  1. 无需任何额外查询即可计算每个节点的子节点计数(如上所述)。
  2. 对给定级别的节点施加命令。其他解决方案是无序的。

【问题讨论】:

  • 在邻接模型中需要 [sub-levels] 的调用实际上是不正确的。你可以这样做:SELECT * FROM adjacency a /* for($number_of_levels_required.. $prevname = $level > 1 ? 'a'+$level-1 : 'a'; */ INNER JOIN adjacency a/*$level*/ ON a/*$level*/.parent_id = /*$prevname*/.id /* endfor*/
  • 如何在路径中分别查询孩子和所有后代。我认为对 Path 的最大缺点是 referential integrity,正如 Bill Karwin 在他的回答中提到的那样。
  • @buffer - “如何查询孩​​子和所有后代”?这是使用路径最简单的部分:SELECT * FROM items WHERE path LIKE 'my/child/path%';。是的,参照完整性会受到影响,但代价是查询效率更高。

标签: sql data-modeling hierarchical-data adjacency-list mptt


【解决方案1】:

您也可以考虑我在对 What is the most efficient/elegant way to parse a flat table into a tree? 的回答中描述的 Closure Table 设计

创建/删除/移动节点所需的调用:

  • 关闭 = 1

获取树所需的调用:

  • 关闭 = 1

获取节点/祖先路径所需的调用:

  • 关闭 = 1

获取子节点数量所需的调用:

  • 关闭 = 1

获取节点深度所需的调用:

  • 关闭 = 1

需要的数据库字段:

  • 邻接 = 多 1 个字段/行
  • 路径 = 多 1 个字段/行
  • MPTT = 2 或 3 个以上字段/行
  • 关闭 = 额外表中的 2 或 3 个字段。此表有 O(n^2) 行最坏情况,但远少于大多数实际情况。

还有其他几个注意事项:

支持无限深度:

  • 邻接 = 是
  • MPTT = 是
  • 路径 = 否
  • 关闭 = 是

支持参照完整性:

  • 邻接 = 是
  • MPTT = 否
  • 路径 = 否
  • 关闭 = 是

我还在我的演讲 Models for Hierarchical Data with SQL and PHP 和我的书 SQL Antipatterns: Avoiding the Pitfalls of Database Programming 中介绍了 Closure Table。

【讨论】:

  • 为什么路径不支持无限深度?
  • Bill- 谢谢,从未听说过闭包表- 看起来很有趣,我会调查的。一件事——你为什么说路径技术不支持无限深度——你是说它是字段中允许的字符串长度的函数吗?
  • 是的,路径设计的最大深度受用于存储路径的字符串长度的限制。因此,例如,如果您的节点标识符是 9 位数字,则 VARCHAR(255) 可以存储深度为 25 的路径(假设您使用单字符分隔符)。
  • @BillKarwin - 他可以只使用varchar(max),抽象地没有限制(IMO),我们可以说其他的受磁盘大小的限制而不是无限的。跨度>
  • 但我承认其他设计对其深度也有有限的限制,至少在您用于节点 ID 或 MPTT 左/右字段的数据类型的有限范围内。正如您所说,还有磁盘空间。尽管如此,路径长度的硬性限制是其他设计所没有的约束。
【解决方案2】:

您的结论的问题在于它忽略了使用树木所涉及的大部分问题。

通过将技术的有效性降低到“调用次数”,您实际上忽略了所有易于理解的数据结构和算法试图解决的问题;也就是说,执行速度最快,内存和资源占用少。

对 SQL 服务器的“调用次数”似乎是一个很好的指标(“看起来更少代码”),但如果结果是程序永远不会完成、运行缓慢或占用大量空间,它实际上是一个无用的指标。​​

通过存储每个节点的路径,您并没有创建树数据结构。相反,您正在创建一个列表。一棵树旨在优化的任何操作都会丢失。

这对于小数据集可能很难看到(在许多小树的情况下,列表更好),尝试一些大小为 500、1000、10k 的数据集的示例——您很快就会明白为什么要存储整个数据集路径不是一个好主意。

【讨论】:

  • @Hogan-“您很快就会明白为什么存储整个路径不是一个好主意”是什么意思? 我会看到什么?并且“任何一棵树旨在优化的操作都会丢失。”那是什么意思?这些都是模糊的陈述——你能提供例子吗?
  • @Yarin - 3 岁的帖子...我会尝试,快速回顾一下:存储整个路径会产生巨大的存储需求,从而以指数方式增加存储大小。在存储方面,树结构允许您将数据分布在树的节点上,因此对于通过树的路径上的排列只存储一次数据。
  • @Hogan- 假设您的意思是与邻接列表相关(MPTT 和闭包技术都需要额外的行,因此需要更大的整体存储空间)。不过,我没有看到巨大的增长问题,它只是一个字符串字段而不是整数字段。即使您的深度级别为 20,您仍然可以轻松地将所有树路径放入 VARCHAR(255) 多达 100 亿行中。
  • @Yarin - 我看到的最后一个实现这样的树的系统在大约 6 行且不超过 50,000 行的深度开始“死亡” - 当然,程序员不是很好,也不是我的项目,所以它可能只是无能(我无法深入分析它 - 双关语)。如果您的方法有效,那就太好了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-20
  • 2021-04-26
  • 1970-01-01
  • 2014-06-26
  • 2014-06-19
  • 1970-01-01
相关资源
最近更新 更多