Django Rest Framework 分页极慢计数
Posted
技术标签:
【中文标题】Django Rest Framework 分页极慢计数【英文标题】:Django Rest Framework pagination extremely slow count 【发布时间】:2015-10-22 18:30:07 【问题描述】:我在 Django Rest 框架中打开了分页,它似乎非常慢。 Count 看起来像是罪魁祸首,由于表中有数百万行,每次返回都需要数百毫秒。
我使用 postgresql 作为数据库。有没有办法不计算行数并仍然使用分页?如果我手动过滤查询集,则在启用此功能之前性能很好。
【问题讨论】:
粘贴代码让问题更清晰。 粘贴代码的方式并不多。我添加的唯一让它变慢的是对 settings.py 的修改:'DEFAULT_PAGINATION_CLASS':'api.pagination.StandardResultsSetPagination' 【参考方案1】:问题在于,用于计数的查询与用于获取数据的潜在复杂查询相同。那比较浪费。 PageNumberPagination
在内部使用 Django 自己的 Paginator
。
为了使计数查询更简单,覆盖 DRF 使用的分页器类:
from django.core.paginator import Paginator
from django.utils.functional import cached_property
from rest_framework.pagination import PageNumberPagination
class FasterDjangoPaginator(Paginator):
@cached_property
def count(self):
# only select 'id' for counting, much cheaper
return self.object_list.values('id').count()
class FasterPageNumberPagination(PageNumberPagination):
django_paginator_class = FasterDjangoPaginator
【讨论】:
非常好的解决方案,谢谢!将使我的应用程序在任何地方都更快:)【参考方案2】:覆盖分页类的get_paginated_response
方法,并且不包括计数。你可以参考PageNumberPagination
类的base implementation 看看你应该返回什么。
from rest_framework.pagination import PageNumberPagination
from collections import OrderedDict # requires Python 2.7 or later
class PageNumberPaginationWithoutCount(PageNumberPagination):
# Set any other options you want here like page_size
def get_paginated_response(self, data):
return Response(OrderedDict([
('next', self.get_next_link()),
('previous', self.get_previous_link()),
('results', data)
]))
然后在您的settings.py
中,将DEFAULT_PAGINATION_CLASS
设置为您的新分页类。
DEFAULT_PAGINATION_CLASS = 'path.to.PageNumberPaginationWithoutCount'
这种方法在example in the pagination docs中使用。
编辑: 从下面的 cmets 听起来这可能不足以防止慢速 sql 查询,因此您可能还需要覆盖 paginate_queryset
。
【讨论】:
"覆盖你的分页类的 get_paginated_response 方法,并且不包括计数。"借调,是的。您可能还需要考虑将 CursorPagination 用于大型数据集,因为它不会随着用户页面越远而变慢,这与页面和偏移样式不同。 我试过了,它从结果中删除了计数,但仍然根据 django 调试工具包运行它。但是,覆盖 paginate_queryset(self, queryset, request, view=None): 并设置 self.count = 0 而不是 self.count = _get_count(queryset) 对其进行排序。我也只是禁用了下一个和上一个,因为我不需要它们。 你是如何覆盖 paginate_queryset 的?【参考方案3】:如果没有计数就可以了,可以使用下一个和上一个链接,以下自定义类。
import sys
from collections import OrderedDict
from django.core.paginator import Paginator
from django.utils.functional import cached_property
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
class CustomPaginatorClass(Paginator):
@cached_property
def count(self):
return sys.maxsize
# To Avoid large table count query, We can use this paginator class
class LargeTablePagination(PageNumberPagination):
django_paginator_class = CustomPaginatorClass
def get_paginated_response(self, data):
return Response(OrderedDict([
('page', self.page.number),
('results', data)
]))
【讨论】:
PageNumberPagination 似乎没有 django_paginator_class 吗?或许曾经如此?不过,这是在正确的轨道上。【参考方案4】:其他答案要么对我不起作用,要么仍在执行额外的COUNT(*)
查询。
这将摆脱所有分页、计数查询,并且只返回 JSON 响应:
from rest_framework.pagination import PageNumberPagination
class NoCountPagination(PageNumberPagination):
page_size = None
def get_paginated_response(self, data):
return Response(
'results', data
)
使用它:
from rest_framework import viewsets
from .models import MyModel
from .serializers import MySerializer
class CustomApiViewSet(viewsets.ReadOnlyModelViewSet):
"""
Simple viewset for viewing MyModels (as a list, or individually).
"""
queryset = MyModel.objects.all()
serializer_class = MySerializer
pagination_class = NoCountPagination
注意,这将返回查询集中的所有行。在几乎所有情况下,我认为最好按原样使用PageNumberPagination
,或者使用上面的@Florian 解决方案来加快速度。
【讨论】:
【参考方案5】:除了 getup8 的响应之外,我还能让它在不返回计数但也不返回所有行的情况下工作(假设您在站点的 REST_FRAMEWORK 设置中将 PAGE_SIZE 设置为合理的数字)。
from rest_framework.pagination import LimitOffsetPagination
class NoCountPaginator(LimitOffsetPagination):
def get_count(self, queryset):
return 99999999
def get_paginated_response(self, data):
return Response(OrderedDict([
('results', data)
]))
并使用它:
from rest_framework import viewsets
from .models import MyModel
from .serializers import MySerializer
class CustomApiViewSet(viewsets.ReadOnlyModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MySerializer
pagination_class = NoCountPaginator
我将 PAGE_SIZE 设置为 25,因此 API 始终只返回前 25 行,并且不再运行计数查询。
【讨论】:
以上是关于Django Rest Framework 分页极慢计数的主要内容,如果未能解决你的问题,请参考以下文章
Django-rest-framework多条件查询/分页/多表Json
关闭 Django Rest Framework ModelViewSet 的自动分页
Django Rest Framework:在 ViewSet 上打开分页(如 ModelViewSet 分页)