【问题标题】:Postgres Materialized Path - What are the benefits of using ltree?Postgres 物化路径 - 使用 ltree 有什么好处?
【发布时间】:2019-12-02 03:42:19
【问题描述】:

Materialized Path 是一种在 SQL 中表示层次结构的方法。每个节点都包含路径本身及其所有祖先 (grandparent/parent/self)。

MP (docs) 的django-treebeard 实现:

  1. 路径的每一步都是固定长度,以实现一致的性能。

  2. 每个节点都包含depthnumchild 字段(以最低的写入成本快速读取)。

  3. 路径字段被索引(使用标准的 b-tree 索引):

    物化路径方法在您的数据库中大量使用 LIKE,其中包含 WHERE path LIKE '002003%' 之类的子句。如果你认为 LIKE 太慢了,你是对的,但是在这种情况下 path 字段在数据库中是索引的,所有不以 % 字符开头的 LIKE 子句都会使用索引。这就是物化路径方法如此快速的原因。

get_ancestorslink)的实现:

将节点与包含当前路径子集的路径匹配(steplen 是步骤的固定长度)。

paths = [
    self.path[0:pos]
    for pos in range(0, len(self.path), self.steplen)[1:]
]
return get_result_class(self.__class__).objects.filter(
    path__in=paths).order_by('depth')

get_descendantslink)的实现:

匹配深度大于自身的节点和以当前路径开始的路径。

return cls.objects.filter(
    path__startswith=parent.path,
    depth__gte=parent.depth
).order_by(
    'path'
)

这种方法的潜在缺点:

  1. 深度嵌套的层次结构会导致路径过长,从而影响读取性能。
  2. 移动节点需要更新所有后代的路径。

Postgres 包含 ltree 扩展,它提供了一个自定义的 GiST 索引 (docs)。

我不清楚ltreedjango-treebeard 的实现有哪些好处。 article 认为只有 ltree 可以回答 get_ancestors 问题,但如前所述,找出节点的祖先(或后代)是微不足道的。

[顺便说一句,如果找到这个 Django ltree 库 - https://github.com/mariocesar/django-ltree]

两种方法都使用索引(django-treebeard 使用 b-tree,ltree 使用自定义 GiST)。我有兴趣了解 ltree GiST 的实现,以及为什么对于这个特定用例(物化路径)而言,它可能是比标准 b 树更有效的索引。

其他链接

What are the options for storing hierarchical data in a relational database?

https://news.ycombinator.com/item?id=709970

【问题讨论】:

    标签: postgresql hierarchy ltree


    【解决方案1】:

    TL;DR 无法使用物化路径完成可重用标签、复杂搜索模式以及针对多个后代节点(或尚未检索到路径的单个节点)的祖先搜索索引。


    对于那些对血腥细节感兴趣的人......

    首先,只有在节点描述中没有重复使用任何标签时,您的问题才有意义。如果是的话,l-tree 确实是两者中唯一的选择。但是具体化路径实现通常不需要这个,所以让我们把它放在一边。

    一个明显的区别在于 l-tree 为您提供的搜索类型的灵活性。考虑这些示例(来自您问题中链接的 ltree 文档):

    foo         Match the exact label path foo
    *.foo.*     Match any label path containing the label foo
    *.foo       Match any label path whose last label is foo
    

    第一个查询显然可以通过物化路径实现。最后一个也是可以实现的,您可以将查询调整为同级查找。但是,中间情况不能通过单个索引查找直接实现。您要么必须将其分解为两个查询(所有后代 + 所有祖先),要么诉诸表扫描。

    还有像这样的非常复杂的查询(也来自文档):

    Top.*{0,2}.sport*@.!football|tennis.Russ*|Spain
    

    物化路径索引在这里是没有用的,需要进行全表扫描来处理这个问题。如果您想将其作为 SARGable 查询执行,则 l-tree 是唯一的选择。

    但是对于标准的分层操作,找到任何一个:

    • 父母
    • 孩子们
    • 后裔
    • 根节点
    • 叶节点

    物化路径与 l-tree 一样有效。与article linked above 不同,使用b-tree 搜索一个共同祖先的所有后代是非常可行的。查询格式 WHERE path LIKE 'A.%' 是 SARGable,前提是您的索引准备妥当(我必须用 varchar_pattern_ops 显式标记我的路径索引才能使其正常工作)。

    此列表中缺少的是为后代找到所有祖先。很遗憾,查询格式WHERE 'A.B.C.D' LIKE path || '.%' 不会使用索引。一些库实现的一种解决方法是从路径中解析出祖先节点,并直接查询它们:WHERE id IN ('A', 'B', 'C')。但是,这仅在您针对已检索其路径的特定节点的祖先时才有效。 l-tree 将在这一点上获胜。

    【讨论】:

      猜你喜欢
      • 2015-01-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-24
      • 2012-01-18
      • 2013-08-08
      相关资源
      最近更新 更多