【问题标题】:How do you split a Django queryset without evaluating it?如何在不评估的情况下拆分 Django 查询集?
【发布时间】:2018-06-05 12:48:41
【问题描述】:

我正在处理一个 超过 500 万个 + 项目的查询集(出于批量 ML 的目的),我需要拆分查询集(这样我就可以执行多线程操作) 无需评估查询集,因为我只需要访问查询集中的每个项目一次,因此我不想缓存评估导致的查询集项目。

是否可以将项目选择到一个查询集中并在不评估的情况下对其进行拆分?还是我必须通过使用 Limits [:size] 查询多个查询集来实现这种行为?

注意: 我知道 Iterable 可用于循环遍历查询集而不对其进行评估,但我的问题与如何拆分查询集(如果可能)然后运行可在每个拆分的查询集上迭代。

【问题讨论】:

  • 你能遍历它并让工作线程获取工作吗?这是我可以想象它通过 Django 处理单个查询的唯一方法。 (几乎只是传递一个线程安全的迭代器。)
  • 一个 django 查询在遇到强制评估之前不会被评估。而且可以切片。所以 myQuery[1000: 11000] 会非常干净地为您提供查询的一部分。不,它不会评估整个事情以获得该切片。

标签: python django performance django-models django-queryset


【解决方案1】:

是的,你可以,从这个gist

根据更新的答案:

def queryset_iterator(queryset, chunk_size=1000):
"""
Iterate over a Django Queryset ordered by the primary key
This method loads a maximum of chunk_size (default: 1000) rows in it's
memory at the same time while django normally would load all rows in it's
memory. Using the iterator() method only causes it to not preload all the
classes.
Note that the implementation of the iterator does not support ordered query sets.
"""
    try:
        last_pk = queryset.order_by('-pk')[:1].get().pk
    except ObjectDoesNotExist:
        return

    pk = 0
    queryset = queryset.order_by('pk')
    while pk < last_pk:
        for row in queryset.filter(pk__gt=pk)[:chunk_size]:
            pk = row.pk
            yield row
        gc.collect()

【讨论】:

    【解决方案2】:

    Django 提供了一些类来帮助您管理分页数据——即,通过“上一个/下一个”链接拆分到多个页面的数据:

    from django.core.paginator import Paginator
    
    object_list = MyModel.objects.all()
    paginator = Paginator(object_list, 10) # Show 10 objects per page, you can choose any other value
    
    for i in paginator.page_range(): # A 1-based range iterator of page numbers, e.g. yielding [1, 2, 3, 4].
        data = iter(paginator.get_page(i))
        # use data
    

    【讨论】:

    • 我相信对于我的问题,分页器似乎是最好的解决方案,为帮助欢呼! N.B.:对于任何阅读页面项目的人,在请求之前不会进行评估,即惰性评估
    • 对于 Django 3.0 page_range 不再可调用,它只是一个属性。而 .get_page(i) => .page(i)
    • @jTiKey 我已经在 django 1.7 中尝试过这个,它是不可调用的,它只是一个列表。我相信这是一个错误
    【解决方案3】:

    我不建议将查询集传递给线程。我知道您要尝试做的事情以及原因,但最好将某种参数集传递给每个线程,然后让线程执行部分查询。 以这种方式工作,您的线程与调用代码不同。

    另一方面,如果您尝试使用线程来解决高 DB 查询导致的滞后问题,您可能会发现使用事务管理是更好的方法。 这个链接link 有一些有用的提示。我用这个而不是线程

    【讨论】:

      【解决方案4】:

      如果您的 django 版本为 1.11 或低于 1.101.9 等,请使用paginator.page(page_no),但要小心当发现无效/未找到页面时,这可能会引发 InvalidPage 异常。

      对于版本 ,使用以下代码:

      from django.core.paginator import Paginator
      
      qs = MyModel.objects.all()
      paginator = Paginator(qs, 20)
      
      for page_no in paginator.page_range:
          current_page = paginator.page(page_no)
          current_qs = current_page.object_list
      

      如果您使用的 django 版本 >= 2.0,请改用paginator.get_page(page_no),但您也可以使用paginator.page(page_no)

      对于 >= 2.0 的版本,请使用以下代码:

      from django.core.paginator import Paginator
      
      qs = MyModel.objects.all()
      paginator = Paginator(qs, 20)
      
      for page_no in paginator.page_range:
          current_page = paginator.get_page(page_no)
          current_qs = current_page.object_list
      

      根据django文档使用paginator.get_page(page_no)的好处如下:

      返回一个有效的页面,即使页面参数不是数字或不是 在范围内。

      而在paginator.page(page_no) 的情况下,如果 page_no 不是数字或超出范围,则必须手动处理异常。

      【讨论】:

      • 欢迎来到 Stack Overflow!虽然这段代码可以回答这个问题,但最好包含一些上下文,解释它是如何工作的以及何时使用它。从长远来看,纯代码的答案往往不太有用。有关更多信息,请参阅How do I write a good answer?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-23
      • 2020-10-31
      • 2011-07-18
      • 1970-01-01
      • 2020-11-04
      • 2018-08-09
      相关资源
      最近更新 更多