在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)

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

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行的序列化程序一样?


问题答案:

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_namewas
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个查询,但是您选择的查询取决于您要实现的目标。