Django DRF使用自定义分页器出现重复查询的原因及优化咨询
Django REST Framework 分页重复查询问题解决方案
问题根因
你遇到的重复查询分为两类,都不属于配置疏漏:
- 分页功能固有查询:DRF 自带的
PageNumberPagination实现分页逻辑时会默认执行2次数据库查询:- 计算总条数
count、总页数num_pages时执行一次 COUNT 类型查询 - 拉取当前页数据时执行一次带 LIMIT/OFFSET 的数据查询
这两次查询是为了返回分页元数据的必要操作,属于正常逻辑。
- 计算总条数
- 额外N+1关联查询:你的过滤条件用到了跨外键关联
owner_id__owner__id,如果DeviceSerializer中也包含owner相关的关联字段,没有提前做关联加载的话,序列化每条设备数据时都会单独发起一次关联表查询,最终产生远多于2次的重复查询,这是常见的性能问题来源。
优化方案
方案1:简化分页逻辑,取消COUNT查询
如果业务场景不需要返回总条数、总页数,可以自定义无COUNT的分页器,将查询次数降低到1次:
class NoCountPageNumberPagination(PageNumberPagination): page_size = 2 page_size_query_param = 'size' max_page_size = 4 def paginate_queryset(self, queryset, request, view=None): page_size = self.get_page_size(request) if not page_size: return None page_number = int(request.query_params.get(self.page_query_param, 1)) offset = (page_number - 1) * page_size # 多查1条判断是否存在下一页 limit = offset + page_size + 1 return list(queryset[offset:limit]) def get_paginated_response(self, data): has_next = len(data) > self.page_size if has_next: data = data[:-1] return Response({ 'page': int(self.request.query_params.get(self.page_query_param, 1)), 'has_next': has_next, 'data': data })
方案2:保留全量分页元数据,优化查询性能
如果需要保留总条数、总页数返回,可做如下优化:
- 给过滤用到的
owner_id字段添加数据库索引,降低COUNT查询的耗时 - 数据实时性要求不高的场景下,可将总条数
count结果存入缓存(如Redis),设置合适的过期时间,避免每次请求都执行COUNT查询 - 给queryset添加关联加载逻辑,解决N+1查询问题:如果是外键/一对一关联用
select_related,一对多/多对多关联用prefetch_related# 示例:提前加载关联的owner数据 queryset = DeviceModel.objects.select_related('owner_id__owner').filter(owner_id__owner__id=request.user.pk)
方案3:代码规范优化
你当前的代码重复定义了queryset,不符合DRF的最佳实践,建议直接重写get_queryset方法,不需要手动重写list方法,DRF会自动执行分页逻辑,减少逻辑疏漏:
class DevicesView(ModelViewSet): serializer_class = DeviceSerializer queryset = DeviceModel.objects.all() pagination_class = MyPageNumberPagination def get_queryset(self): return super().get_queryset().select_related('owner_id__owner').filter(owner_id__owner__id=self.request.user.pk)
内容的提问来源于stack exchange,提问作者dudulu
相关产品推荐
相关产品推荐

