我有一个带有Category
模型的Django站点,该站点的每个实例可以容纳0到n
个子类别。
class Category(models.Model):
...
parent = models.ForeignKey('self', on_delete=models.CASCADE, blank=True, null=True, related_name='subcategories')
类别存储在MySQL数据库中。我需要构建所有类别的嵌套HTML列表。
作为一种轻松而又肮脏的方法,我最初是通过递归函数来完成的。
最初效果很好,但是现在有800多个类别,这导致请求速度大大降低。运行开发服务器时,一次至少要花60秒钟。
这里是功能,略有简化:
def get_category_map(categories, root=False):
category_map = ''
if categories:
if root:
category_map += '<ul id="root">'
else:
category_map += '<ul>'
for category in categories:
category_map += '<li>'
subcategories = category.subcategories.all()
if subcategories.count() == 0:
category_map += '<a class="category-link" href="' + reverse('my_app:category_page', args=(category.pk, category.slug)) + '">' + category.title + '</a>'
else:
category_map += '<span class="category-drop-down">' + category.title + '</span>'
# Recursive call here cripples performance.
category_map += get_category_map(subcategories)
category_map += '</li>'
category_map += '</ul>'
return category_map
该函数的初始调用如下:
get_category_map(Category.objects.filter(parent=None), root=True)
它可以生成我想要的结果,但是会浪费效率和时间。
我了解Python和递归的基本性能问题,但是可以挽救它还是需要一种根本不同的方法?
答案 0 :(得分:2)
这是一个有根据的猜测:您遇到的性能问题很可能是由于命中数据库的查询数量所致,而不是由于生成模板的递归函数所致。
subcategories = category.subcategories.all()
由于您没有使用任何预取功能,因此上面的行将触发您递归访问的每个类别的查询,因此使用800个顶级类别,您将获得800个开始的类别。此外,您还需要对每个计数查询:
if subcategories.count() == 0:
通过使用Django内置的针对模型关系的紧急加载,您可以改善一些情况。 考虑一下,在关系数据库中有效地存储和查询树结构需要一些聪明的算法。 因此,我建议使用(或至少从中获得启发)此Django软件包:
https://django-treebeard.readthedocs.io/en/latest/
Wagtail(支持嵌套类别的流行Django CMS)使用它。
此软件包实现了三种不同的策略来存储和查询树结构:
另一个流行的替代方法是:
https://github.com/django-mptt/django-mptt
在您的情况下,我建议您不要使用“自制”解决方案,因为您已经有很多类别要处理,因此性能对于您的情况已经很重要。从头开始自己实现这些算法并不是一件容易的事。