You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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字段完全没有做预取,每次序列化都会单独发起数据库查询。
优化方案
  1. 调整预取逻辑,使用Prefetch对象只拉取当前用户的关联数据,既减少预取数据量,也省去后续过滤步骤
  2. 序列化器的判断逻辑直接操作内存中已预取的数据集,不发起新的数据库请求
  3. 补充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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 05:54:03