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

时间:2014-10-27 17:30:14

标签: python django-rest-framework django-queryset django-orm django-select-related

我有以下型号:

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个用户,然后在序列化程序的get_is_friend_already

中获得1xN个查询

有没有办法在休息框架方式中避免这种情况?也许类似于将select_related包含的查询传递给具有相关Friendship行的序列化程序?

4 个答案:

答案 0 :(得分:25)

Django REST Framework不能自动为您优化查询,就像Django本身不会这样。有些地方你可以查看提示,including the Django documentation。 Django REST Framework应该自动has been mentioned,尽管存在一些与之相关的挑战。

这个问题非常具体针对您的情况,您使用的是自定义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查询,但您选择的查询取决于您要实现的目标。

答案 1 :(得分:14)

描述 N + 1 问题是 Django REST Framework 性能优化期间的首要问题,因此从各种意见来看,它需要更加可靠的方法然后直接{{1} }或prefetch_related() select_related()查看方法。

根据收集的信息,这是一个强大的解决方案,消除 N + 1 (以OP的代码为例)。它基于装饰器,对于较大的应用程序而言稍微不那么耦合。

<强>串行器:

get_queryset()

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

<强>装饰:

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

这种模式可能看起来有点过分,但它肯定更干,并且优于视图内部的直接查询集修改,因为它允许更多地控制相关实体并消除不必要的相关对象嵌套。

答案 2 :(得分:0)

您可以将视图拆分为两个查询 首先,只获取用户列表(没有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)

答案 3 :(得分:0)

使用此元类DRF optimize ModelViewSet MetaClass

//file HackScala.java
class HackScala {
    public static <T> T method(T obj){
         // erase types -- this is not possible in Scala
         return (T) FooBarJava.method((ComplicatedElement) obj);
    }
}