You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django DRF使用自定义分页器出现重复查询的原因及优化咨询

Django REST Framework 分页重复查询问题解决方案

问题根因

你遇到的重复查询分为两类,都不属于配置疏漏:

  • 分页功能固有查询:DRF 自带的 PageNumberPagination 实现分页逻辑时会默认执行2次数据库查询:
    1. 计算总条数count、总页数num_pages时执行一次 COUNT 类型查询
    2. 拉取当前页数据时执行一次带 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 13:15:02