不让Django DRF ListAPIView 类进行2次查询
在 Django REST Framework (DRF) 的 ListAPIView 或类似视图类中,默认情况下确实会执行两次 SQL 查询:
- 第一次查询:获取符合条件的数据列表(由
get_queryset()决定)。 - 第二次查询:计算总记录数(用于分页,如
count(*))。
如果你 不需要分页,或者 不想额外查询总数,可以采取以下方法优化。
方法 1:禁用分页(完全移除总数查询)
如果不需要分页,直接在视图类中设置 pagination_class = None:
from rest_framework.generics import ListAPIView
from rest_framework.pagination import PageNumberPaginationclass MyListView(ListAPIView):queryset = MyModel.objects.all()serializer_class = MySerializerpagination_class = None # 禁用分页,不会查询总数
这样 DRF 只会执行 get_queryset() 的查询,不会额外计算 count(*)。
方法 2:使用 LimitOffsetPagination 或 CursorPagination 并优化
如果仍然需要分页,但想减少查询次数:
(1)使用 LimitOffsetPagination + 缓存总数
from rest_framework.pagination import LimitOffsetPaginationclass OptimizedPagination(LimitOffsetPagination):def paginate_queryset(self, queryset, request, view=None):# 不计算总数(适用于前端无限滚动等场景)self.count = None # 阻止 count 查询return super().paginate_queryset(queryset, request, view)class MyListView(ListAPIView):queryset = MyModel.objects.all()serializer_class = MySerializerpagination_class = OptimizedPagination # 不查询总数
这样分页仍然可用,但不会执行 count(*) 查询。
(2)使用 CursorPagination(基于游标的分页,不查询总数)
from rest_framework.pagination import CursorPaginationclass MyCursorPagination(CursorPagination):ordering = "-created_at" # 必须指定排序字段page_size = 20class MyListView(ListAPIView):queryset = MyModel.objects.all()serializer_class = MySerializerpagination_class = MyCursorPagination # 游标分页,不查询总数
CursorPagination 适用于 无限滚动 场景,不会计算总数,性能更好。
方法 3:手动覆盖 paginate_queryset 避免 count 查询
如果你想完全控制分页逻辑,可以手动覆盖 paginate_queryset:
from rest_framework.generics import ListAPIView
from rest_framework.response import Responseclass MyListView(ListAPIView):queryset = MyModel.objects.all()serializer_class = MySerializerdef paginate_queryset(self, queryset):# 直接返回数据,不计算总数return list(queryset) # 强制求值,避免惰性查询问题def list(self, request, *args, **kwargs):queryset = self.filter_queryset(self.get_queryset())page = self.paginate_queryset(queryset)if page is not None:serializer = self.get_serializer(page, many=True)return Response(serializer.data)serializer = self.get_serializer(queryset, many=True)return Response(serializer.data)
这样 DRF 不会执行额外的 count(*) 查询。
方法 4:使用 values() 或 iterator() 优化查询
如果数据量很大,可以使用 values() 或 iterator() 减少内存占用:
class MyListView(ListAPIView):def get_queryset(self):return MyModel.objects.values("id", "name") # 只查询必要字段
或者:
class MyListView(ListAPIView):def get_queryset(self):return MyModel.objects.iterator() # 流式查询,减少内存
总结
| 方法 | 适用场景 | 是否查询总数 | 备注 |
|---|---|---|---|
pagination_class = None | 不需要分页 | ❌ 不查询 | 完全禁用分页 |
LimitOffsetPagination + count = None | 需要分页但不想计算总数 | ❌ 不查询 | 适用于无限滚动 |
CursorPagination | 大数据量分页 | ❌ 不查询 | 适用于无限滚动 |
手动覆盖 paginate_queryset | 完全自定义分页逻辑 | ❌ 不查询 | 适用于特殊需求 |
values() / iterator() | 优化查询性能 | 取决于分页 | 减少内存占用 |
如果你的 API 不需要分页,直接 pagination_class = None 是最简单的解决方案。
如果 需要分页但不想计算总数,建议使用 CursorPagination 或自定义 LimitOffsetPagination。
