使用inlineformset_factory(及crispy)时SQL查询过多/渲染缓慢
嘿,这个问题我太熟悉了——Django inline formset的N+1查询坑!咱们一步步来解决它:
为什么会出现大量重复SQL查询?
本质是两个核心原因:
- 每个
BookForm渲染现有书籍时,都会单独查询一次关联的Publisher(默认情况下Django不会自动预取关联对象); - 每个表单的
Publisher外键选择框,默认都会重复执行Publisher.objects.all()来获取选项列表。
解决方案1:给Formset的QuerySet加上预取
在你的作者更新视图里,创建formset的时候,一定要用select_related预取Book关联的Publisher。这样所有书籍的出版社信息会一次性拉取完成,不用每个书单独发起查询:
def author_update(request, pk): author = get_object_or_404(Author, pk=pk) BookFormSet = inlineformset_factory(Author, Book, form=BookForm, extra=1) if request.method == 'POST': # 重点:给queryset加上select_related('publisher') formset = BookFormSet( request.POST, instance=author, queryset=author.books.all().select_related('publisher') ) # 后续提交逻辑... else: formset = BookFormSet( instance=author, queryset=author.books.all().select_related('publisher') ) # 渲染模板...
解决方案2:缓存Publisher选择框的查询结果
如果你的BookForm里用了ModelChoiceField来选择出版社,默认每个表单都会重新查询一遍所有出版社。咱们可以把这个查询结果缓存起来,只执行一次:
在forms.py里修改BookForm:
class BookForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 只查询一次出版社列表,避免重复查询 if not hasattr(self.fields['publisher'], '_cached_queryset'): self.fields['publisher'].queryset = Publisher.objects.all() self.fields['publisher']._cached_queryset = True class Meta: model = Book fields = ['title', 'publisher']
或者更简洁的方式,提前生成固定选项:
# 在forms.py顶部提前获取一次出版社选项 PUBLISHER_CHOICES = [(p.pk, str(p)) for p in Publisher.objects.all()] class BookForm(forms.ModelForm): publisher = forms.ChoiceField(choices=PUBLISHER_CHOICES) class Meta: model = Book fields = ['title', 'publisher']
验证优化效果:用django-debug-toolbar
建议安装django-debug-toolbar,它能直观展示页面触发的所有SQL查询,帮你确认重复查询是不是真的消失了:
pip install django-debug-toolbar
按照官方文档配置好后,刷新页面就能看到查询数量的明显下降,优化效果一目了然。
小提醒:crispy-forms的细节
因为你用了{% crispy formset formset.form.helper %},要确保你的BookForm的helper没有在初始化时动态生成选项——尽量把需要查询的内容提前处理好,别让helper触发额外的查询请求。
这样优化后,页面的SQL查询数量会大幅减少,渲染速度肯定能提上来!
内容的提问来源于stack exchange,提问作者nspo
相关产品推荐
相关产品推荐

