序列化带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实例,不是列表,因此触发报错。
可以通过以下方式实现查询优化:
修改文章详情视图,手动为文章关联的评论构建缓存树
不在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)利用MPTT的缓存机制减少查询
get_cached_trees()会自动将所有评论的父子关系缓存到内存中,评论序列化器中的get_children()方法会直接读取内存中的缓存数据,不会触发额外的SQL查询,最终评论部分的查询次数和单独的评论列表视图一致,仅2次。可选:进一步优化预取逻辑
如果需要先预取文章的评论再构建树,可以修改为: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
相关产品推荐
相关产品推荐

