如何避免Django REST Framework渲染可浏览HTML时的大查询开销?
我来帮你搞定这个性能问题!核心原因是DRF默认的HTML表单渲染(就是你提到的select_multiple.html模板)会查询关联Model2的全部字段来生成下拉选项,而你只需要id,完全没必要拉取这么多冗余数据。下面给你几个实用的解决方案:
方案1:自定义ModelMultipleChoiceField,只查询id
这是最推荐的方法,既保留DRF表单的交互逻辑,又能大幅降低查询开销:
首先创建一个自定义字段,强制只查询id,并且用id作为选项标签:
from django import forms from rest_framework.fields import ModelMultipleChoiceField class IdOnlyModelMultipleChoiceField(ModelMultipleChoiceField): def get_queryset(self): # 仅加载id字段,避免查询其他冗余字段 return super().get_queryset().only('id') def label_from_instance(self, obj): # 直接用id作为选项显示文本,不需要依赖其他字段 return str(obj.id)
然后在你的序列化器里,把对应的多对多字段替换成这个自定义字段:
from rest_framework import serializers from .models import Model1, Model2 class Model1Serializer(serializers.ModelSerializer): # 替换默认的多对多字段为自定义字段 model2 = IdOnlyModelMultipleChoiceField(queryset=Model2.objects.all()) class Meta: model = Model1 fields = ['id', 'model2', '其他字段']
这样DRF渲染HTML表单时,只会执行类似SELECT id FROM app_model2;的轻量查询,响应速度会和JSON渲染接近。
方案2:用普通ChoiceField替代,提前生成id选项
如果你的Model2数据不是实时高频更新,可以直接用ChoiceField,提前生成id选项列表:
from rest_framework import serializers from .models import Model1, Model2 from django.core.cache import cache def get_model2_id_choices(): # 用缓存优化,避免每次请求都查询数据库 choices = cache.get('model2_id_choices') if not choices: # 仅查询id,生成(id, id)的选项元组 choices = [(obj.id, str(obj.id)) for obj in Model2.objects.only('id').iterator()] # 设置缓存过期时间,比如1小时,根据你的数据更新频率调整 cache.set('model2_id_choices', choices, 3600) return choices class Model1Serializer(serializers.ModelSerializer): model2 = serializers.ChoiceField(choices=get_model2_id_choices(), multiple=True) class Meta: model = Model1 fields = ['id', 'model2', '其他字段']
这个方案的查询开销更低,但要注意缓存的过期时间,保证选项和数据库数据一致。
方案3:自定义DRF模板(不推荐,灵活性差)
如果你不想修改序列化器,可以复制DRF的horizontal/select_multiple.html模板到你项目的templates/rest_framework/horizontal/目录下,修改模板中遍历选项的逻辑,让它只依赖id。不过这种方法不够灵活,一旦DRF模板更新你还要同步修改,所以优先推荐前两种方案。
补充说明:为什么JSON渲染快?因为DRF默认的JSON序列化对多对多字段用的是PrimaryKeyRelatedField,只会序列化id,不会查询整个Model2对象;而HTML渲染用的是ModelMultipleChoiceField,默认会查询整个对象来生成选项文本,这就是性能差异的根源。
内容的提问来源于stack exchange,提问作者samb

