【发布时间】:2019-12-02 03:42:19
【问题描述】:
Materialized Path 是一种在 SQL 中表示层次结构的方法。每个节点都包含路径本身及其所有祖先 (grandparent/parent/self)。
MP (docs) 的django-treebeard 实现:
路径的每一步都是固定长度,以实现一致的性能。
每个节点都包含
depth和numchild字段(以最低的写入成本快速读取)。-
路径字段被索引(使用标准的 b-tree 索引):
物化路径方法在您的数据库中大量使用 LIKE,其中包含 WHERE path LIKE '002003%' 之类的子句。如果你认为 LIKE 太慢了,你是对的,但是在这种情况下 path 字段在数据库中是索引的,所有不以 % 字符开头的 LIKE 子句都会使用索引。这就是物化路径方法如此快速的原因。
get_ancestors(link)的实现:
将节点与包含当前路径子集的路径匹配(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_descendants(link)的实现:
匹配深度大于自身的节点和以当前路径开始的路径。
return cls.objects.filter(
path__startswith=parent.path,
depth__gte=parent.depth
).order_by(
'path'
)
这种方法的潜在缺点:
- 深度嵌套的层次结构会导致路径过长,从而影响读取性能。
- 移动节点需要更新所有后代的路径。
Postgres 包含 ltree 扩展,它提供了一个自定义的 GiST 索引 (docs)。
我不清楚ltree 比django-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?
【问题讨论】:
标签: postgresql hierarchy ltree