如何优化在`django-mptt`中添加新节点?

5
我正在创建一个脚本,用于同步两个数据库。数据库中有一些数据应该以树的形式存储,因此我在新数据库中使用 django-mptt。当我同步数据库时,我会从旧数据库中选择新数据,并将其保存到新数据库中。
我想知道是否有更好的方法来添加新节点到树中?现在看起来是这样的:
...
# Add new data to DB
for new_record in new_records:
    # Find appropriate parent using data in 'new_record'
    parent = get_parent(new_record)

    # Create object which should be added using data in 'new_record'
    new_node = MyMPTTModel(...)
    new_node.insert_at(parent, save = True)
    # Similar to:
    # new_node.insert_at(parent, save = False)
    # new_node.save()

但它的工作速度非常慢。我认为它是这样工作的,因为在每次调用insert_at(..., save = True)方法后,django-mptt都应该将新节点写入数据库,并修改已经在数据库中的记录的leftright键。是否有办法使django-mptt每次调用insert_at时修改查询,然后在调用save时一起应用所有更改?或者你知道其他减少执行时间的方法吗?提前致谢。
2个回答

13

首先,不要使用 insert_at。它并不是导致性能缓慢的原因,而且这样做是不必要且难看的。只需设置 node.parent

for new_record in new_records:
    new_node = MyMPTTModel(..., parent=get_parent(new_record))
    new_node.save()

现在是性能问题。 如果您正在使用最新的mptt(git master,而不是0.5.4版),则有一个上下文管理器称为delay_mptt_updates,可以防止mptt进行大量更新,直到您添加了所有节点:

with transaction.atomic():
    with MyMPTTModel.objects.delay_mptt_updates():
        for new_record in new_records:
            new_node = MyMPTTModel(..., parent=get_parent(new_record))
            new_node.save()

或者,如果你几乎要触及整棵树,你可以通过使用disable_mptt_updates来进一步加快速度,在最后重建整棵树:

with transaction.atomic():
    with MyMPTTModel.objects.disable_mptt_updates():
        for new_record in new_records:
            new_node = MyMPTTModel(..., parent=get_parent(new_record))
            new_node.save()
    MyMPTTModel.objects.rebuild()

@craigds - 当更新一个已存在的节点并将其移动到新的父节点时,这个方法是否有效?我尝试使用最近的MPTT中的上下文管理器,但即使重建后也似乎无效。 - snakesNbronies

1

Django-MPTT为您维护了一棵树形结构。因此,在每次insert_at时,它将修改插入节点右侧的所有节点-这就是为什么您会遇到性能问题的原因。

一种方法是手动构建树形结构,而不使用django-mptt

因此,您需要获取新记录,并根据它们确定必须修改树中旧节点的方式。由于您只插入数据,因此只有左和右属性会更改,但级别不会更改,因此这应该使其变得更容易。一旦您知道要修改哪些节点,就可以使用一个update事务(编辑)来修改它们。

然后,您可以开始插入新数据。同样,最快的方法是计算每个新条目的左、右和级别值,然后执行一个bulk_insert(Django>=1.4)。这样做将导致仅两个数据库操作,显然在数据库事务方面应该更快。

然而,这种方法需要一些聪明的方式来找出如何更改树中的旧节点。最简单的方法是将整个树转储到Python结构中,然后在该结构上找出更改。但是,如果您的树非常大,由于内存限制,这种方法将不可行。

现在还不确定是否有更有效的方法来解决这个问题。也许StackOverflow上的其他人有一些很酷的想法...

编辑

对于update混淆感到抱歉。我指的是一个事务。在这种情况下,我通常会执行原始的SQL查询,其中我执行update tbname set ... where id=1; update tbname set ... where id=2;因此,我在一个SQL查询中执行多个SQL语句。从我的经验来看,数据库的昂贵部分不是执行语句,而是事务本身,因为存在网络延迟、数据库锁等。因此,只有一个事务可以使数据库尽可能快。但是,不确定如何使用查询集在Django中执行此操作。我通常会执行原始的SQL查询。


如何仅使用一个“update”语句修改整个分支? - Serhii Holinei

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接