Django REST Framework如何解决SerializerMethodField的N+1查询问题
问题原因
你的代码存在3个核心问题导致N+1查询:
- 原
setup_eager_loading方法没有返回预取后的QuerySet,Django的QuerySet是不可变对象,prefetch_related不会修改原实例,必须返回新的QuerySet才会生效,等于原来的预取逻辑完全没有起作用。 obj.history_set.get(user=user)这类写法会直接触发新的数据库查询,完全不会复用已经预取到内存中的关联数据,prefetch的缓存只有操作内存中已拉取的数据集时才会生效。is_wl字段完全没有做预取,每次序列化都会单独发起数据库查询。
优化方案
- 调整预取逻辑,使用
Prefetch对象只拉取当前用户的关联数据,既减少预取数据量,也省去后续过滤步骤 - 序列化器的判断逻辑直接操作内存中已预取的数据集,不发起新的数据库请求
- 补充
is_wl字段对应的预取逻辑
完整修改后的代码
serializers.py
from django.db.models import Prefetch # 记得导入你项目中的Track模型 class VideoSerializer(serializers.ModelSerializer): is_viewed = serializers.SerializerMethodField() is_favorited = serializers.SerializerMethodField() is_wl = serializers.SerializerMethodField() class Meta: model = Video fields = ( "pk", "is_viewed", "is_favorited", "is_wl", ) @staticmethod def setup_eager_loading(queryset, user): # 仅预取当前用户的关联数据,用to_attr自定义属性名避免覆盖默认反向引用 queryset = queryset.prefetch_related( Prefetch( 'history_set', queryset=History.objects.filter(user=user), to_attr='user_history' ), Prefetch( 'favorite_set', queryset=Favorite.objects.filter(user=user), to_attr='user_favorite' ), Prefetch( 'track_set', # 如果你Track模型的video外键设置了related_name,这里替换成对应的值 queryset=Track.objects.filter(playlist__user=user, playlist__is_wl=True), to_attr='user_wl_track' ) ) return queryset # 必须返回修改后的queryset def get_is_viewed(self, obj): user = self.context["request"].user if not user.is_authenticated: return False # 直接判断内存中预取列表的长度,无额外数据库查询 return len(obj.user_history) > 0 def get_is_favorited(self, obj): user = self.context["request"].user if not user.is_authenticated: return False return len(obj.user_favorite) > 0 def get_is_wl(self, obj): user = self.context["request"].user if not user.is_authenticated: return False return len(obj.user_wl_track) > 0
视图调用示例
class VideoListView(generics.ListAPIView): serializer_class = VideoSerializer def get_queryset(self): queryset = Video.objects.all() # 传入当前用户调用预取方法 queryset = VideoSerializer.setup_eager_loading(queryset, self.request.user) return queryset
优化效果
总查询数从N+1降到固定4次:1次查询Video列表,3次分别预取当前用户的观看历史、收藏、待看列表,所有布尔值判断都在内存中完成,无额外查询。
内容的提问来源于stack exchange,提问作者Jvn
相关产品推荐
相关产品推荐

