我正在开发一个带有Django后端的webapp,它为前端提供了一个Django REST Framework API。我最近遇到了一些性能问题,因此我开始调查每个端点的性能 - 其中大多数都向数据库发出了太多查询。
我有几个与预取相关的问题,我无法弄清楚,但这是(我认为)最简单的问题。我已经尽可能地实施了select_related
和prefetch_related
,感谢文档和this excellent post关于如何考虑热切加载的问题。
对于一个特定的模型,我已经很好地减少了查询的数量,但我无法弄清楚为什么我仍然有一些重复。
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)
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
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
urls.py
router.register(r'groups', ReadingGroupViewset)
setup_eager_loading()
方法减少了将数据库中的所有12个ReadingGroup
实例从40个检索到17个的查询数量,其中12个查询是重复的。每个重复查询都是在每个模型实例上单独调用模型__str__
方法的结果。
我最初认为这是因为DRF docs中的某些内容表明__str__
方法仅在可浏览的API中使用:
模型的内置 str 方法将用于生成用于填充choices属性的对象的字符串表示。这些选项用于在可浏览的API中填充选定的HTML输入。
然而,情况似乎并非如此。我为Django调试工具栏实现了一个解决方法,用于在HTML中包装JSON响应(因为工具栏只配置HTML响应),将?format=json
添加到端点请求(因此可以浏览可浏览的API),但重复的查询仍然存在发出的。
DRF是否调用__str__
方法,尽管没有使用可浏览的API进行请求,或者我的模型是否存在其他问题?
这可能是由于与使用ModelViewset
相关的行为造成的吗?我正在进行此测试的端点仅为/api/groups/
,这是register()
命令自动生成的URL之一。
编辑:@serg下面的评论让我思考 - 如果它在查询序列化程序中没有的字段,我想它不会受到伤害预取它也是。将book_type
添加到setup_eager_loading
可以摆脱重复的查询:
@staticmethod
def setup_eager_loading(queryset):
#select_related for 'to-one' relationships
queryset = queryset.select_related('owner')
queryset = queryset.select_related('book_type')
#prefetch_related for 'to-many' relationships
queryset = queryset.prefetch_related('users')
return queryset
我在上面的代码中添加了该字段,因此__str__
方法访问关系字段时更清楚。