Django Rest Framework视图QuerySet定义:为何启动时触发数据库查询?
问题背景
DRF官方文档给出的示例视图如下:
from django.contrib.auth.models import User from myapp.serializers import UserSerializer from rest_framework import generics from rest_framework.permissions import IsAdminUser class UserList(generics.ListCreateAPIView): queryset = User.objects.all() serializer_class = UserSerializer permission_classes = [IsAdminUser]
有人依据Django文档,担心调用.all()会在视图类创建时就触发数据库查询,觉得这种写法不够合理。查看DRF源码后发现,框架会在每次请求时重新评估QuerySet:
assert self.queryset is not None, ( "'%s' should either include a `queryset` attribute, " "or override the `get_queryset()` method." % self.__class__.__name__ ) queryset = self.queryset if isinstance(queryset, QuerySet): # Ensure queryset is re-evaluated on each request. queryset = queryset.all() return queryset
于是产生疑问:把示例中的queryset改成User.objects(移除.all())是否更优,以此避免启动时触发数据库查询?
解答
首先明确一个关键知识点:Django的QuerySet是惰性求值的。User.objects.all()只是创建了一个QuerySet对象,并不会立即执行数据库查询,只有当真正需要获取数据(比如序列化、迭代、调用len()等)时,SQL才会被执行。所以官方示例的写法根本不会在启动时触发查询,担心完全是多余的。
再看DRF的源码逻辑:框架在get_queryset()方法中,会先检查queryset是否是QuerySet实例,如果是,就调用.all()生成一个全新的QuerySet。这么做的目的是确保每次请求都拿到未被缓存的、全新的查询集合,避免复用之前请求的查询结果。
如果改成queryset = User.objects,这里的User.objects是Manager对象,不是QuerySet。此时源码中的isinstance(queryset, QuerySet)判断不成立,不会执行.all()。后续视图逻辑需要处理的是QuerySet,直接传入Manager会导致报错——因为Manager不支持分页、序列化等QuerySet才有的操作。
所以结论很明确:不能这么修改,官方示例的写法才是正确且规范的。
内容的提问来源于stack exchange,提问作者run_the_race

