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

Django DRF多对多关联SerializerMethodField引发重复查询问题

优化Django ORM查询,解决N+1问题

首先,咱们先定位问题根源:

你的MyModel和MyModelRelation是多对一关系(MyModelRelation的obj是ForeignKey),所以obj.relation返回的是一个QuerySet而不是单个实例。你在序列化器里直接调用obj.relation.tag_list(),相当于对每个MyModel都要查询它的所有关联MyModelRelation,再对每个MyModelRelation查询关联的Tag——哪怕你加了prefetch_related,这种写法也没利用到预取的结果,自然会触发大量重复查询。

另外,tag_list()方法里的values_list('name', flat=True).distinct(),如果没有基于预取的内存数据处理,也会额外触发数据库查询。

下面分两种场景给你优化方案:


场景1:每个MyModel对应一个MyModelRelation(一对一关系)

如果你的业务逻辑是一个MyModel只会关联一个MyModelRelation,那首先把外键改成OneToOneField,这样反向关联会直接返回单个实例,避免QuerySet的困扰:

1. 修改模型关系

class MyModelRelation(models.Model):
    # 把ForeignKey改成OneToOneField,确保一对一关联
    obj = models.OneToOneField(MyModel, related_name='relation', on_delete=models.CASCADE)
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    tags = models.ManyToManyField(Tag)

2. 优化序列化器,利用预取数据

直接从预取的tags集合中提取名称,避免调用实例方法触发新查询:

class MyModelSerializer(serializers.ModelSerializer):
    tags_list = serializers.SerializerMethodField(read_only=True)
    
    def get_tags_list(self, obj):
        # 从预取的tags中提取名称并去重,全程在内存处理,不触发新查询
        return list({tag.name for tag in obj.relation.tags.all()})
    
    class Meta:
        model = MyModel
        fields = ['name', 'tags_list']

3. 视图保持预取配置

class ObjListView(ListAPIView):
    serializer_class = MyModelSerializer
    queryset = MyModel.objects.all()
    
    def get_queryset(self):
        # select_related关联查询MyModelRelation,prefetch_related预取关联的tags
        return super().get_queryset().select_related('relation').prefetch_related('relation__tags')

场景2:每个MyModel对应多个MyModelRelation(一对多关系)

如果一个MyModel会关联多个MyModelRelation,咱们直接在数据库层面聚合所有关联的标签,一次查询搞定所有数据:

1. 使用数据库聚合(以PostgreSQL为例)

借助ArrayAgg聚合函数,直接在MyModel的查询中把所有关联标签的名称聚合为一个列表:

from django.db.models import OuterRef
from django.contrib.postgres.aggregates import ArrayAgg
from rest_framework import serializers

class MyModelSerializer(serializers.ModelSerializer):
    # 直接用ListField接收聚合后的标签列表
    tags_list = serializers.ListField(child=serializers.CharField(), read_only=True)
    
    class Meta:
        model = MyModel
        fields = ['name', 'tags_list']

class ObjListView(ListAPIView):
    serializer_class = MyModelSerializer
    
    def get_queryset(self):
        # 用annotate在数据库层面聚合所有关联的标签名称,自动去重
        return MyModel.objects.annotate(
            tags_list=ArrayAgg('relation__tags__name', distinct=True)
        )

2. 非PostgreSQL环境的替代方案

如果用的是MySQL等不支持ArrayAgg的数据库,可以用StringAgg把标签名拼接成字符串,再在序列化器里拆分:

from django.db.models import StringAgg

class MyModelSerializer(serializers.ModelSerializer):
    tags_list = serializers.SerializerMethodField(read_only=True)
    
    def get_tags_list(self, obj):
        return obj.tags_list.split(',') if obj.tags_list else []
    
    class Meta:
        model = MyModel
        fields = ['name', 'tags_list']

class ObjListView(ListAPIView):
    serializer_class = MyModelSerializer
    
    def get_queryset(self):
        return MyModel.objects.annotate(
            tags_list=StringAgg('relation__tags__name', separator=',', distinct=True)
        )

额外提示

你模型里的ChartField应该是笔误,记得改成CharField哦!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 00:12:44