2

我面临与 django-mptt 相关的戏剧性性能问题。这是我的情况:

  • 我有一个测验课
  • 我有一个带有 FK 到 Quizz 和一个 FK 到 Category 类的 Question 类
  • 我有一个属于 MPTT 树的 Category 类(因为我的分类是分层的)

现在,我有一个包含 7 个问题的实际测验和一个管理视图,该视图将问题显示为 QuizzAdmin 视图的内联,并且内联带有 Category as Select 字段。

然后麻烦来了:

  • 我已经将问题加载为 prefetch_related (甚至尝试让 questions__category 像这样加载)
  • 尽管如此,我还是看到我的调试工具栏显示了在模板渲染时发生的一系列 16 个查询 (template/edit_inline/tabular.html)。在我的开发笔记本电脑上,这意味着加载这一切需要 1 分钟(在我的带有实际数据的测试环境中,这意味着 10 分钟!)

这 16 个查询是以下的连续查询:(请注意我正在使用虚拟类别进行测试)

SELECT "quizz_category"."id", "quizz_category"."parent_id", "quizz_category"."name", 
"quizz_category"."name_en", "quizz_category"."name_fr", "quizz_category"."lft",
"quizz_category"."rght", "quizz_category"."tree_id", "quizz_category"."level",
"quizz_category"."description", "quizz_category"."description_en",
"quizz_category"."description_fr" FROM "quizz_category" ORDER BY
"quizz_category"."tree_id" ASC, "quizz_category"."lft" ASC

SELECT "quizz_category"."id", "quizz_category"."parent_id", "quizz_category"."name", 
"quizz_category"."name_en", "quizz_category"."name_fr", "quizz_category"."lft",
"quizz_category"."rght", "quizz_category"."tree_id", "quizz_category"."level",
"quizz_category"."description", "quizz_category"."description_en",
"quizz_category"."description_fr" FROM "quizz_category" WHERE ("quizz_category"."lft" <= 3
AND "quizz_category"."rght" >= 6 AND "quizz_category"."tree_id" = 1 ) ORDER BY
"quizz_category"."lft" ASC

知道我可以做些什么来减少查询数量吗?

提前谢谢洛杉矶

[编辑 1]

有一个愚蠢的事情解释了问题的一半:我的类别的 __unicode__() 正在查看对象的父母的 __unicode__() (幸运的是我的树只有 2 级深)

现在在我的最佳配置中,对于 8 个条目,我仍然有 9 次“SELECT ... FROM quizz_category”(没有 WHERE 子句),据说是为了构建 Select 字段的选择。

任何人都知道如何缓存这个查询并且只运行一次?

注意:我当前的最佳配置是在 QuestionInline 中有 .select_related('category')


class QuestionInline(admin.TabularInline): # admin.StackedInline
    model = Question
    extra = 0
    ordering = ['position',]

    def queryset(self, request):
        return super(QuestionInline, self).queryset(request).select_related('category')


class QuizzAdmin(admin.ModelAdmin):
    list_display = ["name","rating_scale"]
    inlines = [QuestionInline]
    fieldsets = (
        (None, {'fields': (('name'), ('type',), 'description',
                           'rating_scale' )}),
    )

    def queryset(self, request):
        if getattr(self,'is_change_list', False):
            # it's a changelist view, we don't need details on ForeignKey-accessible objects
            return super(QuizzAdmin, self).queryset(request)
        else:
            return super(QuizzAdmin, self).queryset(request).select_related('rating_scale')

    def changelist_view(self, request, extra_context=None):
        self.is_change_list = True
        return super(QuizzAdmin, self).changelist_view(request, extra_context)

class Category(AbstractAnalyticTreeCategory):
    description         = BusinessTextField(_("description"))  # basically a text field of mine

    tree = AnalyticTreeManager()

    def __unicode__(self):
        return self.name

class Quizz(models.Model):
    name                = models.CharField(_("name of the quizz"), unique=True, max_length=60)
    description         = BusinessTextField(_("description"))
    type                = models.CharField(_("type"), choices=QUIZZ_TYPE_CHOICES, default=QUIZZ_SELF_EVALUATION, null=False, blank=False, max_length=2)
    rating_scale        = models.ForeignKey(MCQScale, verbose_name=_("applicable rating scale"), on_delete=models.PROTECT)


    def __unicode__(self):
        return self.name



class Question(models.Model):
    position = models.IntegerField(verbose_name=_("order index"), help_text=_("Order in which the question will appear."))
    quizz               = models.ForeignKey(Quizz, verbose_name=_("Related quizz"), null=False, blank=False, related_name='questions')
    title               = BusinessCharField(_("item"), max_length=60, null=True, blank=True)
    text                = BusinessTextField(_("question text"),)
    category            = TreeForeignKey(Category, verbose_name=_("dimension"), null=True, blank=False, on_delete=models.SET_NULL)

    def __unicode__(self):
        return self.title

以下是调试工具栏对这些查询的说明(都一样):

选择“quizz_category”。“id”,“quizz_category”。“parent_id”,“quizz_category”。“name”,“quizz_category”。“name_en”,“quizz_category”。“name_fr”,“quizz_category”。“lft”,“ quizz_category"."rght", "quizz_category"."tree_id", "quizz_category"."level", "quizz_category"."description", "quizz_category"."description_en", "quizz_category"."description_fr" FROM "quizz_category" ORDER BY "quizz_category"."tree_id" ASC, "quizz_category"."lft" ASC 3,68816058264% 1,66 Sel Expl 连接:默认隔离级别:读取已提交事务状态:在事务 /Library/Python/2.7/site-packages/django/contrib/staticfiles/handlers.py 中调用(72) return self.application(environ, start_response) /Library/Python/2.7/site-packages/django/contrib/admin/widgets.py in render(263) output = [self.widget.render(name, value, *args, **kwargs)] 49

{{ field.contents|换行符}}

50 {% else %} 51
{{ field.field.errors.as_ul }} 52
{{ field.field }} 53
{% endif %} 54
55
{% endfor %} /Library/Python/2.7/site-packages/ django/contrib/admin/templates/admin/edit_inline/tabular.html

4

2 回答 2

1

我正在使用 django ,并且在 Django 表单中为 ModelChoiceField 或 ModelMultipleChoiceField 缓存查询集选择中1.10的任何解决方案都不适用于我。我最终使用了基于's的不同解决方案。我的表单从 560 个 SQL 查询变为 10 个。我的代码:django-mptttree_item_iterator

from categories.models import Category
from mptt.utils import tree_item_iterator


def get_tree_choices(queryset, level_indicator='+----', ancestors=False):
    choices = []
    for node, tree in tree_item_iterator(queryset, ancestors=ancestors):
        name=''
        if ancestors:
            for i in tree['ancestors']:
                name+=level_indicator
            name+=' %s' % node.name
        else:
            name = str(node)
        choices.append((node.id, name))
    return choices


# Register your models here.
class CompanyAdminForm(forms.ModelForm):
    category=forms.ChoiceField(choices=get_tree_choices(Category.objects.all()))

    error_messages = {
        'invalid_vat_id': _('Invalid Portuguese VAT ID')
    }

    class Meta:
        exclude = ('category',)
        ...

    def save(self, commit=True):
        company=super(CompanyAdminForm, self).save(commit=False)
        company.category=Category.objects.get(id=self.cleaned_data['category'])
        company.save()
        return company
    ...


@admin.register(Company)
class CompanyAdmin(admin.ModelAdmin):
        ...

仍然不是 100% 满意,但至少它很快。

于 2016-10-12T14:28:49.083 回答
0

所以.. 我找到了一个解决方案,它的灵感来自Django 表单中 ModelChoiceField 或 ModelMultipleChoiceField 的缓存查询集选择,我在那篇文章中对此进行了描述。

由于内联工厂机制,Django admin 有一个奇怪的 1 查询开销(我没有深入研究)。这解释了为什么在正常情况下您有 2*k + 1 个查询(k=内联表单集中的项目数)。

问题希望得到解决。

于 2013-09-07T21:40:32.807 回答