亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

在Django REST框架中優化數據庫查詢

在Django REST框架中優化數據庫查詢

有只小跳蛙 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

TA貢獻1802條經驗 獲得超4個贊

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

TA貢獻1831條經驗 獲得超10個贊

述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
?
守著星空守著你

TA貢獻1799條經驗 獲得超8個贊

您可以將視圖分為兩個查詢。

首先,僅獲取“用戶”列表(無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
  • 3 回答
  • 0 關注
  • 1169 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號