为了账号安全,请及时绑定邮箱和手机立即绑定

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

/ 猿问

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

慕莱坞5435954 2019-12-26 11:02:53

我有以下型号:


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


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


查看完整描述

3 回答

?
慕虎7371278

Django REST Framework无法像Django本身一样为您自动优化查询。您可以在一些地方找到提示,包括Django文档。它已经提到的是Django的REST框架应该自动,虽然有与之相关的一些挑战。


这个问题是非常特定于您的情况的,在这种情况下,您正在使用一个自定义项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_name是friends),这会将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个查询,但是您选择的查询取决于您要实现的目标。


查看完整回答
反对 2019-12-26
?
慕哥6287543

述N + 1个问题是在一个首要问题Django的REST框架性能优化,所以从各种观点,它需要更多的固体的方法,然后直接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()

这种模式可能看起来像是一个过大的杀手,但是它肯定更干了,并且比直接在视图内部修改查询集更具优势,因为它可以更好地控制相关实体,并消除了不必要的相关对象嵌套。


查看完整回答
反对 2019-12-26
?
守着星空守着你

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

首先,仅获取“用户”列表(无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)


查看完整回答
反对 2019-12-26

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信