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

序列化带MPTT评论的文章时,如何优化N+1 SQL查询?

问题:Django REST Framework中关联MPTT评论的查询优化

我在DRF中实现文章详情接口,关联了MPTT结构的评论。单独获取评论列表时用get_cached_trees()能做到仅2次SQL查询的优化,但在获取文章详情并嵌套评论时,尝试用prefetch_related和Prefetch都没成功——直接用prefetch_related无效,用Prefetch则报错'list' object has no attribute '_add_hints'。但在Django模板中渲染相同的评论结构时不存在这个问题,求解决方法。

现有代码

文章详情序列化器

class ArticleDetailSerializer(serializers.ModelSerializer):
    author = ArticleAuthorSerializer(read_only=True)
    comments = CommentSerializer(many=True, read_only=True)

    class Meta:
        model = Article
        fields = '__all__'

评论序列化器

class CommentSerializer(serializers.ModelSerializer):

    class Meta:
        model = Comment
        fields = '__all__'

    def get_fields(self):
        fields = super(CommentSerializer, self).get_fields()
        fields['children'] = CommentSerializer(many=True, required=False, source='get_children')
        return fields

已优化的评论列表视图

class CommentListAPIView(generics.ListAPIView):
    serializer_class = serializers.CommentSerializer
    queryset = Comment.objects.all().get_cached_trees()

尝试优化但失败的文章详情视图

class ArticleDetailAPIView(generics.RetrieveAPIView):
    serializer_class = serializers.ArticleDetailSerializer
    queryset = Article.custom.all()
    lookup_field = 'slug'

    def get_queryset(self):
        queryset = self.queryset.prefetch_related(Prefetch('comments', queryset=Comment.objects.all().get_cached_trees()))
        return queryset
解决方案

核心问题是get_cached_trees()返回的是列表对象,而Prefetch的queryset参数要求传入Django的QuerySet实例,不是列表,因此触发报错。

可以通过以下方式实现查询优化:

  1. 修改文章详情视图,手动为文章关联的评论构建缓存树
    不在get_queryset中使用Prefetch,而是在获取文章实例后,单独为该文章的评论构建缓存树,并覆盖实例的默认评论关联:

    class ArticleDetailAPIView(generics.RetrieveAPIView):
        serializer_class = serializers.ArticleDetailSerializer
        queryset = Article.custom.all()
        lookup_field = 'slug'
    
        def retrieve(self, request, *args, **kwargs):
            instance = self.get_object()
            # 获取当前文章的所有评论并构建缓存树
            instance.comments = Comment.objects.filter(article=instance).get_cached_trees()
            serializer = self.get_serializer(instance)
            return Response(serializer.data)
    
  2. 利用MPTT的缓存机制减少查询
    get_cached_trees()会自动将所有评论的父子关系缓存到内存中,评论序列化器中的get_children()方法会直接读取内存中的缓存数据,不会触发额外的SQL查询,最终评论部分的查询次数和单独的评论列表视图一致,仅2次。

  3. 可选:进一步优化预取逻辑
    如果需要先预取文章的评论再构建树,可以修改为:

    def retrieve(self, request, *args, **kwargs):
        # 先预取文章关联的评论
        instance = self.get_object().prefetch_related('comments')
        # 通过主键过滤并构建缓存树
        comments = Comment.objects.filter(pk__in=[c.pk for c in instance.comments]).get_cached_trees()
        instance.comments = comments
        serializer = self.get_serializer(instance)
        return Response(serializer.data)
    

    不过get_cached_trees()本身已经是高效的2次查询,第一种方法足以满足需求。

内容的提问来源于stack exchange,提问作者Razilator

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 05:05:21