在Django REST框架中优化数据库查询 [英] Optimizing database queries in Django REST framework

查看:207
本文介绍了在Django REST框架中优化数据库查询的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下模型:

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

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

这些模型在以下视图和序列化器中使用:

And those models are used in the following view and serializer:

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

所以基本上对于由$ code> GetAllUsers 视图返回的每个用户,我想打印出用户是否是请求者的朋友(实际上我应该检查from_和to_friend,但不会关于问题的问题)

So basically, for each user returned by the GetAllUsers view, I want to print out whether the user is a friend with the requester (actually I should check both from_ and to_friend, but does not matter for the question in point)

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

What I see is that for N users in database, there is 1 query for getting all N users, and then 1xN queries in the serializer's get_is_friend_already

有没有办法以其余的框架方式避免这种情况?可能有些类似于将 select_related 的查询传递给具有相关友谊行的序列化程序

Is there a way to avoid this in the rest-framework way? Maybe something like passing a select_related included query to the serializer that has the relevant Friendship rows?

推荐答案


Django REST框架无法自动优化查询,与Django本身不同。有些地方可以查看提示,包括Django文档 。它已经提到了,Django REST框架应该自动,尽管有一些与此相关的挑战。

Django REST Framework cannot automatically optimize queries for you, in the same way that Django itself won't. There are places you can look at for tips, including the Django documentation. It has been mentioned that Django REST Framework should automatically, though there are some challenges associated with that.

此问题对您的案例非常具体,您在哪里使用自定义的 SerializerMethodField 对每个返回的对象发出请求。因为您正在提出新的请求(使用 Friends.objects manager),因此很难优化查询。

This question is very specific to your case, where you are using a custom SerializerMethodField that makes a request for each object that is returned. Because you are making a new request (using the Friends.objects manager), it is very difficult to optimize the query.

您可以通过不创建新的查询器,而不是从其他地方获取好友数量,使问题更好。这将需要在 Friendship 模型上创建一个向后关系,很可能通过字段上的 related_name 参数,所以您可以预取所有 Friendship 对象。但是,如果您需要完整的对象,而不仅仅是对象的计数,这只能有用。

You can make the problem better though, by not creating a new queryset and instead getting the friend count from other places. This will require a backwards relation to be created on the Friendship model, most likely through the related_name parameter on the field, so you can prefetch all of the Friendship objects. But this is only useful if you need the full objects, and not just a count of the objects.

这将导致一个视图和序列化程序类似于以下内容: / p>

This would result in a view and serializer similar to the following:

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()),您可以包括使用反向关系计数在queryset中注释行。这将在您的 get_queryset 方法中完成,方法是将 .annotate(friends_count = Count(friends))添加到结束(如果 related_name 朋友),将设置 friends_count 属性与朋友的数量。

If you just need a count of the objects (similar to using queryset.count() or queryset.exists()), you can include annotate the rows in the queryset with the counts of reverse relationships. This would be done in your get_queryset method, by adding .annotate(friends_count=Count("friends")) to the end (if the related_name was friends), which will set the friends_count attribute on each object to the number of friends.

这将导致一个视图和序列化程序类似于以下内容:

This would result in a view and serializer similar to the following:

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个查询,但您选择的则取决于您要实现的内容。

Both of these solutions will avoid N+1 queries, but the one you pick depends on what you are trying to achieve.

这篇关于在Django REST框架中优化数据库查询的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆