Django嵌套查询性能

时间:2015-10-30 20:34:25

标签: python django performance postgresql django-queryset

我对自定义表单的模型看起来像这样。

class Form(models.Model):
    # some fields

class FormSection(models.Model):
    form = models.ForeignKey(Form, related_name='section_set')

class FormWidget(models.Model):
    section_set = models.ManyToManyField(FormSection, related_name='widget_set')

class FormEntry(models.Model):
    user = models.ForeignKey(User, related_name="form_entry_set")
    form = models.ForeignKey(Form)
    date = models.DateTimeField(default=datetime.datetime.now)

class SectionEntry(models.Model):
    section = models.ForeignKey(FormSection)
    form_entry = models.ForeignKey(FormEntry, related_name="section_entry_set")

class WidgetEntry(models.Model):
    widget = models.ForeignKey(FormWidget)
    section_entry = models.ForeignKey(SectionEntry, related_name="widget_entry_set")
    value = models.CharField(max_length=255)

对于我的一个观点,我需要检索:

根据用户列表,在特定时间段内为列表中的每个用户获取所有FormEntry个。并且对于每个FormEntry,获取表单数据(WidgetEntry.value)

并在字典中将其结构化。

{"<form_entry_pk>": {
        "date": "2015-06-26",
        "<section_name>": {
            "<widget_name>": "<widget_value>"
        },
        "<section_name>": {
            "<widget_name>": "<widget_value>"
        },
        "<section_name>": {
            "<widget_name>": "<widget_entry_value>",
            "<widget_name>": "<widget_entry_value>",
            "<widget_name>": "<widget_entry_value>",
            "<widget_name>": "<widget_entry_value>",
            "<widget_name>": "<widget_entry_value>",
            "<widget_name>": "<widget_entry_value>",
            "<widget_name>": "<widget_entry_value>",
            "<widget_name>": "<widget_entry_value>",
            "<widget_name>": "<widget_entry_value>"
        }
    },
 "<form_entry_pk>": {
        ...
    },
...
}

目前,我正在通过循环遍历每个查询集中的项来检索数据。像这样的东西。

for user in users:
    form_data = {}    
    form_entries = user.form_entry_set.filter(form=form, date__range=[start_date, end_date]).order_by('date')
    for form_entry in form_entries:
        form_data[form_entry.pk] = {}
        form_data[form_entry.pk]['date'] = form_entry.date
        for section_entry in form_entry.section_entry_set.all():
            form_data[form_entry.pk][section_entry.section.name] = {}
            for widget_entry in section_entry.widget_entry_set.all():
                form_data[form_entry.pk][section_entry.name][widget_entry.widget.name] = widget_entry.value

这会产生我想要的结果。但是花了很长时间。在某些情况下最多2分钟。在使用django-debug-toolbar进行一些调试之后,我注意到有大量重复的SQL查询。 (即4031 queries including 4024 duplicates

我的问题是:我该怎么做才能减少查询次数。 我尝试使用defer()only()(在代码中排除使其更具可读性)。但他们似乎并没有那么多帮助。

提前感谢!

1 个答案:

答案 0 :(得分:2)

我认为这里的关键是在初始查询中使用select_related。假设你的模型是正确的外键,那应该解决顶部的所有关系(通过跟随模型中定义的FK在幕后生成JOIN查询)。

所以第一个查询集就变成了这样:

form_entries = user.form_entry_set.filter(form=form, date__range=[start_date, end_date]).order_by('date').select_related()

然后,您可以从返回的查询集访问各种模型中的所有列,这将消除嵌套循环的需要。 (您应该能够遍历查询集本身。)

根据OP的评论进行编辑:

prefetch_related处理除 FK 一对一之外的其他类型的关系,结果select_related仅限于此。由于您的模型中定义了ManyToManyField,因此prefetch_related可能会在您的具体情况下更好地运作。