django 中的高效分页和数据库查询

Posted

技术标签:

【中文标题】django 中的高效分页和数据库查询【英文标题】:Efficient pagination and database querying in django 【发布时间】:2013-04-16 05:13:36 【问题描述】:

有一些用于 django 分页的代码示例,我不久前使用过。我可能错了,但是在查看代码时,它看起来会浪费大量内存。我一直在寻找更好的解决方案,代码如下:

# in views.py
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

... 
...    

def someView():
    models = Model.objects.order_by('-timestamp')
    paginator = Paginator(models, 7)
    pageNumber = request.GET.get('page')

    try: 
        paginatedPage = paginator.page(pageNumber)
    except PageNotAnInteger: 
        pageNumber = 1
    except EmptyPage: 
        pageNumber = paginator.num_pages
    models = paginator.page(pageNumber)

    return render_to_resp ( ..... models ....)

我不确定这段代码的细微之处,但从它的外观来看,第一行代码从数据库中检索每个模型并将其推入。然后它被传递到 Paginator,它根据用户在 html GET 中所在的页面将其分块。分页器是否以某种方式使其可以接受,或者这完全是内存效率低下的?如果效率低下,如何改进?

另外,一个相关的话题。如果有人这样做:

   Model.objects.all()[:40]

这段代码是否意味着所有模型都被推入内存,我们拼接出其中的 40 个?哪个不好。还是说我们只查询并推送40个对象到内存周期?

感谢您的帮助!

【问题讨论】:

【参考方案1】:

mymodel.objects.all() 产生一个查询集,而不是一个列表。查询集是惰性的——在你真正尝试使用它们之前,不会发出任何请求,也不会做任何事情。此外,对查询集进行切片不会将整个该死的东西加载到内存中,只是为了获取一个子集,而是在访问数据库之前为 SQL 查询添加限制和偏移量。

【讨论】:

【参考方案2】:

使用分页器时没有任何内存效率低下的问题。查询集是惰性求值的。在您的电话Paginator(models, 7) 中,models 是一个查询集,到目前为止尚未评估。所以,直到现在数据库还没有被击中。此时内存中也没有包含所有模型实例的列表。

当你想获得一个页面时,即paginatedPage = paginator.page(pageNumber),在这个查询集上进行切片,只有在这个时候数据库被命中并且数据库返回一个包含模型实例的查询集。然后切片只返回页面上应该存在的对象。因此,只有切片对象会进入内存中的列表。假设在一页上显示 10 个对象,只有这 10 个对象会留在内存中。

当有人这样做时;

Model.objects.all()[:40]

当您对列表进行切片时,会创建一个新列表。在您的情况下,将创建一个仅包含 40 个元素的列表,并将存储在内存中的某个位置。不会有其他列表,因此不会有任何列表包含内存中 Model 的所有实例。

【讨论】:

是的,但是 ..models 是整个数据库中的所有模型对象,它就在第一行加载。你是说分页器喜欢杀死它还是什么?对于您的第二个答案,是的,创建了一个新列表,但旧列表仍然存在,对吗?或者它只是噗消失了? 对于第二个答案,由于旧列表的引用计数将为0,因此在垃圾收集发生时将被垃圾收集,因为无法到达旧列表。 对于第一个答案,是的,models 是数据库中的所有模型对象,并且在您的整个视图期间都将在内存中。而你想要它,否则 django 应该如何知道在特定页面上显示哪些对象? Django 只有在你提供所有对象然后告诉它你想要哪个页面然后 django 会进行切片并返回一个新列表时才能做到这一点。 @akshar raaj 请阅读手册并了解查询集的工作原理。你在所有方面都完全错了。 @brunodesthuilliers 你现在可以检查一下吗?【参考方案3】:

使用上面的信息我想出了一个视图函数装饰器。 json_list_objects 将 djanog 对象带到 django 对象的已知关系字段的 json-ready python dicts 中,并将 jsonified 列表作为 count: results: 返回。

其他人可能会觉得它很有用。

def with_paging(fn):
  """
  Decorator providing paging behavior.  It is for decorating a function that 
  takes a request and other arguments and returns the appropriate query
  doing select and filter operations.  The decorator adds paging by examining
  the QueryParams of the request for page_size (default 2000) and 
  page_num (default 0).  The query supplied is used to return the appropriate
  slice. 
  """
  @wraps(fn)
  def inner(request, *args, **kwargs):
    page_size = int(request.GET.get('page_size', 2000))
    page_num = int(request.GET.get('page_num', 0))
    query = fn(request, *args, **kwargs)
    start = page_num * page_size
    end = start + page_size
    data = query[start:end]
    total_size = query.count()
    return json_list_objects(data, overall_count=total_size)
  return inner

【讨论】:

以上是关于django 中的高效分页和数据库查询的主要内容,如果未能解决你的问题,请参考以下文章

Django分页和查询参数的问题

Redis实现分页和多条件模糊查询方案

DjangoQuerySet的分页和排序

Spring Data Cassandra 中的分页和排序查询

基于 mybatis 的分页和过滤查询

Mysql分页和排序子查询聚集函数