Django多对多字段Inline管理页查询性能优化求助
问题
我定义了Page模型,其name和description字段为与Content模型关联的多对多字段。我创建了对应的Inline管理页面,但使用Django Silk进行性能测试时,发现产生了11次数据库查询,主要原因是Language字段被重复获取至少4次。相关代码如下:
models.py
class Page(models.Model): created_at = models.DateTimeField(auto_now_add=True) name = models.ManyToManyField('content.Content', blank=True, related_name="as_page_names_set") description = models.ManyToManyField('content.Content', blank=True, related_name="as_page_description_set") slug = models.SlugField() class Language(models.Model): language_name = models.CharField(max_length=16, unique=True) iso_code = models.CharField(max_length=6, unique=True) class Content(models.Model): language = models.ForeignKey(Language, models.CASCADE) content = RichTextUploadingField(max_length=2048)
admin.py
class PageNameAdmin(admin.StackedInline): extra = 1 model = Page.name.through verbose_name = "Page Name" form = PageContentForm class PageDescriptionAdmin(admin.StackedInline): extra = 1 model = Page.description.through form = PageContentForm verbose_name = "Page Description" @admin.register(Page) class PageAdmin(admin.ModelAdmin): inlines = [PageNameAdmin] exclude = ('name', 'description') def get_queryset(self, request: HttpRequest) -> QuerySet[Any]: return super().get_queryset(request).prefetch_related('name', 'description')
forms.py
class PageContentForm(forms.ModelForm): language = forms.ModelChoiceField(queryset=Language.objects.all(), required=True) content = RichTextFormField(required=True) FIELDS = ['content', 'language'] class Meta: model = Page.name.through fields = ('page', 'content', 'language') def get_initial_for_field(self, field: Field, field_name: str) -> Any: if self.instance.pk and field_name in self.FIELDS: return getattr(self.instance.content, field_name) return super().get_initial_for_field(field, field_name) def clean(self) -> Dict[str, Any]: if self.instance.pk is None: self.cleaned_data['content'] = self.create_content_obj(**self.cleaned_data) self.cleaned_data.pop('language') else: self.cleaned_data['content'] = self.get_content_obj(**self.cleaned_data) return super().clean() def create_content_obj(self, content = None, language = None, *args, **kwargs): return Content.objects.create(content=content, language=language) def get_content_obj(self, *args, **kwargs): content_obj = self.instance.content for k in self.FIELDS: setattr(content_obj, k, kwargs[k]) content_obj.save() return content_obj
问题根源与优化方案
1. 未预取Content关联的Language数据
在PageAdmin的get_queryset方法中,你只预取了name和description对应的Content对象,但没有同时预取Content关联的language字段。当每个Inline表单执行get_initial_for_field方法时,访问self.instance.content.language都会触发一次独立的数据库查询,形成N+1查询问题,这是重复获取Language的核心原因。
2. Language查询集未缓存(可选优化)
PageContentForm中language字段的queryset=Language.objects.all(),每次初始化表单都会执行一次查询。虽然Language数据量通常不大,但缓存这个查询集可以避免无意义的重复查询。
优化后的代码
1. 修改admin.py,预取Content关联的Language
from django.db.models import Prefetch @admin.register(Page) class PageAdmin(admin.ModelAdmin): inlines = [PageNameAdmin] exclude = ('name', 'description') def get_queryset(self, request: HttpRequest) -> QuerySet[Any]: return super().get_queryset(request).prefetch_related( # 预取name关联的Content,并同时预取Content的language Prefetch('name', queryset=Content.objects.select_related('language')), # 如果启用description的Inline,同样需要添加该预取 Prefetch('description', queryset=Content.objects.select_related('language')) )
2. 优化forms.py,缓存Language查询集
class PageContentForm(forms.ModelForm): language = forms.ModelChoiceField(queryset=Language.objects.all(), required=True) content = RichTextFormField(required=True) FIELDS = ['content', 'language'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 缓存Language查询集,避免重复执行查询 if not hasattr(self.fields['language'], '_cached_queryset'): self.fields['language'].queryset = Language.objects.all() self.fields['language']._cached_queryset = True class Meta: model = Page.name.through fields = ('page', 'content', 'language') # 其余方法保持不变...
内容的提问来源于stack exchange,提问作者mohamed naser
相关产品推荐
相关产品推荐

