Django 嵌套查询性能

Django Nested Query Performance

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

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() (在代码中排除以使其更具可读性)。但他们似乎并没有那么多帮助。

提前致谢!

我认为这里的关键是在初始查询中使用 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 在您的特定情况下效果更好的原因。