在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个用户,然后在序列化程序的get_is_friend_already
有没有办法在rest框架的方式来避免这种情况? 也许像传递一个select_related
包含查询到具有相关Friendship
行的序列化程序?
Django REST Framework不能自动为你优化查询,就像Django本身不会这样。 有些地方可以查看提示, 包括Django文档 。 有人提到 ,Django REST框架应该会自动完成,不过也有一些相关的挑战。
这个问题是非常具体的你的情况,你正在使用一个自定义的SerializerMethodField
,为每个返回的对象请求。 因为您正在发出新的请求(使用Friends.objects
pipe理器),所以优化查询是非常困难的。
您可以通过不创build新的查询集,而是从其他地方获得朋友数来更好地解决问题。 这将需要在Friendship
模型上创build一个向后的关系,很可能是通过字段的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
方法中完成,在最后(如果related_name
是friends
)添加.annotate(friends_count=Count("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
这两个解决scheme都将避免N + 1个查询,但是您select的查询取决于您要实现的目标。
在Django REST Framework性能优化过程中,所描述的N + 1问题是头号问题,所以从各种观点来看,需要更加坚实的方法,然后在get_queryset()
方法中直接使用prefetch_related()
或get_queryset()
方法。
基于收集的信息,这里是一个强大的解决scheme,消除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
这里我们使用静态类方法来构build特定的查询集。
装饰:
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
序列化方法中定义的模型提取相关logging。
视图:
class GetAllUsers(generics.ListAPIView): serializer_class = GetAllUsersSerializer @setup_eager_loading def get_queryset(self): return User.objects.all()
这种模式可能看起来像是矫枉过正,但它肯定比DRY更直接,并且比视图内的直接查询集修改更具优势,因为它允许更多地控制相关实体并消除相关对象的不必要的嵌套。
一种优化查询的方法是…
只获得你所需要的
- 默认情况下,Django请求表的所有托pipe列并填充一个Python对象。
- 如果只需要表中的列的子集,请考虑使用
values
和values_list
。 这些方法跳过创build一个复杂的python对象的步骤,而是使用字典,元组甚至普通的值。 他们甚至可以直接通过关系来处理列。
例如…
# Retrieve values as a dictionary >>> Book.objects.values('title', 'author__name') <QuerySet [{'author__name': u'Nikolai Gogol', 'title': u'The Overcoat'}, {'author__name': u'Leo Tolstoy', 'title': u'War and Peace'}]> # Retrieve values as a tuple >>> Book.objects.values_list('title', 'author__name') <QuerySet [(u'The Overcoat', u'Nikolai Gogol'), (u'War and Peace', u'Leo Tolstoy')]> >>> Book.objects.values_list('title') <QuerySet [(u'The Overcoat',), (u'War and Peace',)]> # With one value, it is easier to flatten the list >>> Book.objects.values_list('title', flat=True) <QuerySet [u'The Overcoat', u'War and Peace']>
您可以将视图分成两个查询。
首先,只获取用户列表(不含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)