【问题标题】:Django REST Framework: Setting up prefetching for nested serializersDjango REST Framework:为嵌套序列化程序设置预取
【发布时间】:2016-09-23 21:06:20
【问题描述】:

我的带有 DRF API 的 Django 应用程序运行良好,但随着数据库填充了实际数据,我开始遇到性能问题。我使用 Django Debug Toolbar 进行了一些分析,发现我的许多端点在返回数据的过程中发出了数十到数百个查询。

我预料到了这一点,因为我之前没有对数据库查询进行任何优化。但是,现在我正在设置预取,当该序列化程序嵌套在不同的序列化程序中时,我无法正确使用预取的序列化程序数据。我一直使用这个awesome post 作为如何考虑不同预取方式的指南。

目前,当我点击/api/readinggroups/ 端点时,我的ReadingGroup 序列化程序确实可以正确预取。我的问题是/api/userbookstats/ 端点,它返回所有UserBookStats 对象。相关的序列化程序UserBookStatsSerializer 有一个嵌套的ReadingGroupSerializer

模型、序列化器和视图集如下:

models.py

class ReadingGroup(models.model):
    owner = models.ForeignKeyField(settings.AUTH_USER_MODEL)
    users = models.ManyToManyField(settings.AUTH_USER_MODEL)
    book_type = models.ForeignKeyField(BookType)
    ....
    <other group related fields>

   def __str__(self):
     return '%s group: %s' % (self.name, self.book_type)

class UserBookStats(models.Model):
    reading_group = models.ForeignKey(ReadingGroup)
    user = models.ForeignKey(settings.AUTH_USER_MODEL)
    alias = models.CharField()

    total_books_read = models.IntegerField(default=0)
    num_books_owned = models.IntegerField(default=0)
    fastest_read_time = models.IntegerField(default=0)
    average_read_time = models.IntegerField(default=0)

serializers.py

class ReadingGroupSerializer(serializers.ModelSerializer):
    users = UserSerializer(many = True,read_only=True)
    owner = UserSerializer(read_only=True)

    class Meta:
      model = ReadingGroup
      fields = ('url', 'id','owner', 'users')

    @staticmethod
    def setup_eager_loading(queryset):
      #select_related for 'to-one' relationships
      queryset = queryset.select_related('owner')

      #prefetch_related for 'to-many' relationships
      queryset = queryset.prefetch_related('users')

      return queryset

class UserBookStatsSerializer(serializers.HyperlinkedModelSerializer):
    reading_group = ReadingGroupSerializer()
    user = UserSerializer()
    awards = AwardSerializer(source='award_set', many=True)

    class Meta:
      model = UserBookStats
      fields = ('url', 'id', 'alias', 'total_books_read', 'num_books_owned', 
              'average_read_time', 'fastest_read_time', 'awards')

    @staticmethod
    def setup_eager_loading(queryset):
      #select_related for 'to-one' relationships
      queryset = queryset.select_related('user')

      #prefetch_related for 'to-many' relationships
      queryset = queryset.prefetch_related('awards_set')

      #setup prefetching for nested serializers
      groups = Prefetch('reading_group', queryset ReadingGroup.objects.prefetch_related('userbookstats_set'))        
      queryset = queryset.prefetch_related(groups)

      return queryset

views.py

class ReadingGroupViewset(views.ModelViewset):

  def get_queryset(self):
    qs = ReadingGroup.objects.all()
    qs = self.get_serializer_class().setup_eager_loading(qs)
    return qs

class UserBookStatsViewset(views.ModelViewset):

  def get_queryset(self):
    qs = UserBookStats.objects.all()
    qs = self.get_serializer_class().setup_eager_loading(qs)
    return qs

我已经优化了ReadingGroup 端点的预取(实际上我发布了关于消除该端点here 的重复查询),现在我正在处理UserBookStats 端点。

我遇到的问题是,在UserBookStatsSerializer 中使用我当前的setup_eager_loading,它似乎没有使用ReadingGroupSerializer 中的急切加载方法设置的预取。我对Prefetch 对象的语法仍然有些模糊——我受到this 的启发,尝试了这种方法。

显然UserBookStatsViewsetget_queryset 方法不会为ReadingGroup 对象调用setup_eager_loading,但我确信有一种方法可以完成相同的预取。

【问题讨论】:

  • 您是在询问优雅的解决方案还是任何解决方案? queryset = queryset.prefetch_related('reading_group', 'reading_group__users', 'reading_group__owner') 应该可以正常工作。
  • 任何解决方案都会很棒 - 哇,太棒了。这很好用,所以如果你将它作为答案提交,我会接受。它取代了Prefetch 对象,这是我希望将其用于一种优雅的解决方案,可以解耦预取,但这非常有效。谢谢!

标签: django django-rest-framework


【解决方案1】:

prefetch_related()支持使用双下划线语法预取内部关系:

queryset = queryset.prefetch_related('reading_group', 'reading_group__users', 'reading_group__owner') 

我认为 Django REST 没有提供任何开箱即用的优雅解决方案来自动获取所有必要的字段。

【讨论】:

    【解决方案2】:

    除了手动预取所有嵌套关系之外,还有一个名为 django-auto-prefetching 的包,它会自动遍历模型和序列化程序上的相关字段,以查找所有需要在 prefetch_relatedselect_related 中提及的模型来电。您需要做的就是将AutoPrefetchViewSetMixin 添加到您的视图集中:

    from django_auto_prefetching import AutoPrefetchViewSetMixin
    
    class ReadingGroupViewset(AutoPrefetchViewSetMixin, views.ModelViewset):
    
      def get_queryset(self):
        qs = ReadingGroup.objects.all()
        return qs
    
    class UserBookStatsViewset(AutoPrefetchViewSetMixin, views.ModelViewset):
    
      def get_queryset(self):
        qs = UserBookStats.objects.all()
        return qs
    

    可以在 ViewSet 的 get_queryset 方法中添加具有更复杂 Prefetch 对象的任何额外预取。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-12-15
      • 2016-02-09
      • 2018-07-06
      • 1970-01-01
      • 2018-05-20
      • 2014-08-20
      • 2014-12-21
      相关资源
      最近更新 更多