Materialized Path 是一种在 SQL 中表示层次结构的方法。每个节点都包含路径本身及其所有祖先 ( grandparent/parent/self
)。
django-treebeard
MP(文档)的实现:
路径的每一步都是固定长度,以实现一致的性能。
每个节点都包含
depth
和numchild
字段(以最低的写入成本快速读取)。路径字段被索引(使用标准的 b 树索引):
物化路径方法在数据库中大量使用 LIKE,其中包含 WHERE path LIKE '002003%' 之类的子句。如果你认为 LIKE 太慢了,你是对的,但是这种情况下 path 字段在数据库中是索引的,所有不以 % 字符开头的 LIKE 子句都会使用索引。这就是使物化路径方法如此快速的原因。
实施get_ancestors
(链接):
将节点与包含当前路径子集的路径匹配(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
(链接):
匹配深度大于自身的节点和以当前路径开始的路径。
return cls.objects.filter(
path__startswith=parent.path,
depth__gte=parent.depth
).order_by(
'path'
)
这种方法的潜在缺点:
- 深度嵌套的层次结构会导致路径过长,这会损害读取性能。
- 移动一个节点需要更新所有后代的路径。
Postgres 包含ltree
提供自定义GiST索引 ( docs ) 的扩展。
我不清楚哪些好处ltree
提供了 overdjango-treebeard
的实施。本文认为只能ltree
回答这个get_ancestors
问题,但如前所述,找出节点的祖先(或后代)是微不足道的。
[顺便说一句,如果找到这个 Djangoltree
库 - https://github.com/mariocesar/django-ltree]。
两种方法都使用索引(django-treebeard
使用 b-tree,ltree
使用自定义 GiST)。我有兴趣了解ltree
GiST 的实现,以及为什么对于这个特定用例(物化路径),它可能是比标准 b 树更有效的索引。
附加链接