在 Django REST 框架中优化数据库查询

2025-04-10 09:46:00
admin
原创
11
摘要:问题描述:我有以下模型:class User(models.Model): name = models.Charfield() email = models.EmailField() class Friendship(models.Model): from_friend = mode...

问题描述:

我有以下模型:

class User(models.Model):
    name = models.Charfield()
    email = models.EmailField()

class Friendship(models.Model):
    from_friend = models.ForeignKey(User)
    to_friend = models.ForeignKey(User)

这些模型用于以下视图和序列化器:

class GetAllUsers(generics.ListAPIView):
    authentication_classes = (SessionAuthentication, TokenAuthentication)
    permission_classes = (permissions.IsAuthenticated,)
    serializer_class = GetAllUsersSerializer
    model = User

    def get_queryset(self):
        return User.objects.all()

class GetAllUsersSerializer(serializers.ModelSerializer):

    is_friend_already = serializers.SerializerMethodField('get_is_friend_already')

    class Meta:
        model = User
        fields = ('id', 'name', 'email', 'is_friend_already',)

    def get_is_friend_already(self, obj):
        request = self.context.get('request', None)

        if request.user != obj and Friendship.objects.filter(from_friend = user):
            return True
        else:
            return False

所以基本上,对于视图返回的每个用户GetAllUsers,我想打印出该用户是否是请求者的朋友(实际上我应该检查 from_ 和 to_friend,但对于重点问题来说并不重要)

我看到的是,对于数据库中的 N 个用户,有 1 个查询用于获取所有 N 个用户,然后序列化器中有 1xN 个查询get_is_friend_already

有没有办法在 rest-framework 中避免这种情况?也许像将select_related包含的查询传递给具有相关行的Friendship序列化程序?


解决方案 1:

Django REST Framework 无法自动为您优化查询,就像 Django 本身不会那样。您可以查看一些地方获取提示,包括 Django 文档。有人提到Django REST Framework 应该自动执行此操作,尽管这会带来一些挑战。

这个问题非常具体,因为您使用的是自定义请求,SerializerMethodField每个返回的对象都会发出请求。由于您正在发出新请求(使用Friends.objects管理器),因此很难优化查询。

不过,您可以通过不创建新查询集而是从其他地方获取好友计数来改善问题。这将需要在模型上创建向后关系Friendship,最有可能是通过related_name字段上的参数,这样您就可以预取所有对象Friendship。但这仅在您需要完整对象而不仅仅是对象计数时才有用。

这将导致类似于以下内容的视图和序列化器:

class Friendship(models.Model):
    from_friend = models.ForeignKey(User, related_name="friends")
    to_friend = models.ForeignKey(User)

class GetAllUsers(generics.ListAPIView):
    ...

    def get_queryset(self):
        return User.objects.all().prefetch_related("friends")

class GetAllUsersSerializer(serializers.ModelSerializer):
    ...

    def get_is_friend_already(self, obj):
        request = self.context.get('request', None)

        friends = set(friend.from_friend_id for friend in obj.friends)

        if request.user != obj and request.user.id in friends:
            return True
        else:
            return False

如果您只需要对象数量(类似于使用queryset.count()queryset.exists()),则可以在查询集中注释行,其中包含反向关系的数量。这将在您的get_queryset方法中完成,方法是将其添加.annotate(friends_count=Count("friends"))到末尾(如果related_namefriends),这会将每个对象的属性设置friends_count为朋友的数量。

这将导致类似于以下内容的视图和序列化器:

class Friendship(models.Model):
    from_friend = models.ForeignKey(User, related_name="friends")
    to_friend = models.ForeignKey(User)

class GetAllUsers(generics.ListAPIView):
    ...

    def get_queryset(self):
        from django.db.models import Count

        return User.objects.all().annotate(friends_count=Count("friends"))

class GetAllUsersSerializer(serializers.ModelSerializer):
    ...

    def get_is_friend_already(self, obj):
        request = self.context.get('request', None)

        if request.user != obj and obj.friends_count > 0:
            return True
        else:
            return False

这两种解决方案都可以避免 N+1 查询,但是选择哪种解决方案取决于您要实现的目标。

解决方案 2:

描述的N+1问题是Django REST Framework性能优化期间的头号问题,因此从各种观点来看,它需要比直接prefetch_related()select_related()视图get_queryset()方法更可靠的方法。

根据收集到的信息,这里有一个可以消除N+1的强大解决方案(以 OP 的代码为例)。它基于装饰器,对于较大的应用程序来说耦合度略低。

序列化器:

class GetAllUsersSerializer(serializers.ModelSerializer):
    friends = FriendSerializer(read_only=True, many=True)

    # ...

    @staticmethod
    def setup_eager_loading(queryset):
        queryset = queryset.prefetch_related("friends")

        return queryset

这里我们使用静态类方法来构建特定的查询集。

装饰者:

def setup_eager_loading(get_queryset):
    def decorator(self):
        queryset = get_queryset(self)
        queryset = self.get_serializer_class().setup_eager_loading(queryset)
        return queryset

    return decorator

此函数修改返回的查询集以便获取setup_eager_loading序列化器方法中定义的模型的相关记录。

看法:

class GetAllUsers(generics.ListAPIView):
    serializer_class = GetAllUsersSerializer

    @setup_eager_loading
    def get_queryset(self):
        return User.objects.all()

这种模式可能看起来有点过度,但它肯定更加 DRY,并且比在视图内部直接修改查询集更具优势,因为它允许对相关实体进行更好的控制,并消除了相关对象的不必要嵌套。

解决方案 3:

使用此元类DRF优化ModelViewSet MetaClass

from django.utils import six

@six.add_metaclass(OptimizeRelatedModelViewSetMetaclass)
class MyModelViewSet(viewsets.ModelViewSet):
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer

解决方案 4:

您可以将视图拆分为两个查询。

首先,仅获取用户列表(不带is_friend_already字段)。这只需要一个查询。

其次,获取 request.user 的好友列表。

第三,根据用户是否在 request.user 的好友列表中修改结果。

class GetAllUsersSerializer(serializers.ModelSerializer):
    ... 


class UserListView(ListView):
    def get(self, request):
        friends = request.user.friends
        data = []
        for user in self.get_queryset():
            user_data = GetAllUsersSerializer(user).data
            if user in friends:
                user_data['is_friend_already'] = True
            else:
                user_data['is_friend_already'] = False
            data.append(user_data)
        return Response(status=200, data=data)
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2482  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1533  
  PLM(产品生命周期管理)项目对于企业优化产品研发流程、提升产品质量以及增强市场竞争力具有至关重要的意义。然而,在项目推进过程中,范围蔓延是一个常见且棘手的问题,它可能导致项目进度延迟、成本超支以及质量下降等一系列不良后果。因此,有效避免PLM项目范围蔓延成为项目成功的关键因素之一。以下将详细阐述三大管控策略,助力企业...
plm系统   0  
  PLM(产品生命周期管理)项目管理在企业产品研发与管理过程中扮演着至关重要的角色。随着市场竞争的加剧和产品复杂度的提升,PLM项目面临着诸多风险。准确量化风险优先级并采取有效措施应对,是确保项目成功的关键。五维评估矩阵作为一种有效的风险评估工具,能帮助项目管理者全面、系统地评估风险,为决策提供有力支持。五维评估矩阵概述...
免费plm软件   0  
  引言PLM(产品生命周期管理)开发流程对于企业产品的全生命周期管控至关重要。它涵盖了从产品概念设计到退役的各个阶段,直接影响着产品质量、开发周期以及企业的市场竞争力。在当今快速发展的科技环境下,客户对产品质量的要求日益提高,市场竞争也愈发激烈,这就使得优化PLM开发流程成为企业的必然选择。缺陷管理工具和六西格玛方法作为...
plm产品全生命周期管理   0  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用