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
在您的特定情况下效果更好的原因。
我的自定义表单模型看起来像这样。
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
在您的特定情况下效果更好的原因。