我有一个带有 Category
模型的 Django 网站,每个实例都可以容纳零到 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 和递归的基本性能问题,但是这可以挽救吗,还是需要一种根本不同的方法?
最佳答案
这是一个有根据的猜测:您遇到的性能问题很可能是由于访问数据库的查询数量所致,而不是由于生成模板的递归函数所致。
子类别=category.subcategories.all()
由于您没有使用任何预取,因此上面的行将触发对您递归访问的每个类别的查询,因此对于 800 个顶级类别,您将获得 800 个类别。此外,您还对每一项执行计数查询:
如果 subcategories.count() == 0:
您可以通过使用 Django 内置的模型关系预加载来改进一些事情。 考虑一下,在关系数据库中有效地存储和查询树结构需要一些聪明的算法。 所以我建议使用(或者至少从中汲取灵感)这个 Django 包:
https://django-treebeard.readthedocs.io/en/latest/
它由 Wagtail 使用,Wagtail 是一种支持嵌套类别的流行 Django CMS。
该包实现了三种不同的策略来存储和查询树结构:
- 邻接列表
- 物化路径
- 嵌套集
另一种流行的替代方案是:
https://github.com/django-mptt/django-mptt
我建议不要在您的情况下使用“自制”解决方案,因为您已经有相当多的类别需要处理,因此性能在您的情况下已经很重要)。从头开始自己实现这些算法绝非易事。
关于python - 递归 Python 函数削弱 Django 站点性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54036266/