物化路径是一种在 SQL 中表示层次结构的方法。每个节点包含路径本身及其所有祖先(grandparent/parent/self
).
The django-treebeard
实施MP(docs https://django-treebeard.readthedocs.io/en/latest/mp_tree.html):
-
路径的每个步骤都是固定长度,以实现一致的性能。
-
每个节点包含depth
and numchild
字段(以最小的写入成本快速读取)。
-
路径字段已建立索引(使用标准 B 树索引):
物化路径方法在数据库中大量使用 LIKE,以及 WHERE path LIKE '002003%' 等子句。如果您认为 LIKE 太慢,那么您是对的,但在这种情况下,路径字段在数据库中建立了索引,并且所有不以 % 字符开头的 LIKE 子句都将使用该索引。这就是物化路径如此快速逼近的原因。
实施get_ancestors
(link https://github.com/django-treebeard/django-treebeard/blob/8042ee939cb45394909237da447f8925e3cc6aa3/treebeard/mp_tree.py#L1052):
将节点与包含当前路径子集的路径(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 https://github.com/django-treebeard/django-treebeard/blob/8042ee939cb45394909237da447f8925e3cc6aa3/treebeard/mp_tree.py#L958):
匹配深度大于自身的节点和以当前路径开头的路径。
return cls.objects.filter(
path__startswith=parent.path,
depth__gte=parent.depth
).order_by(
'path'
)
这种方法的潜在缺点:
- 深度嵌套的层次结构会导致路径过长,从而损害读取性能。
- 移动节点需要更新所有后代的路径。
Postgres 包括ltree
提供自定义扩展GiST https://en.wikipedia.org/wiki/GiST index (docs https://www.postgresql.org/docs/current/ltree.html).
不太清楚有什么好处ltree
提供超过django-treebeard
的实施。这article http://patshaughnessy.net/2017/12/15/looking-inside-postgres-at-a-gist-index认为只有ltree
可以回答get_ancestors
问题,但如前所述,找出节点的祖先(或后代)是微不足道的。
[顺便说一句,我发现了这个 Djangoltree
库 - https://github.com/mariocesar/django-ltree]。
两种方法都使用索引(django-treebeard
使用b树,ltree
使用自定义 GiST)。我有兴趣了解该协议的实施ltree
对于这个特定的用例(物化路径),GiST 以及为什么它可能是比标准 B 树更有效的索引。
附加链接
在关系数据库中存储分层数据有哪些选项? https://stackoverflow.com/questions/4048151/what-are-the-options-for-storing-hierarchical-data-in-a-relational-database
https://news.ycombinator.com/item?id=709970 https://news.ycombinator.com/item?id=709970