【问题标题】:How to iterate a large table in Django without running out of memory?如何在 Django 中迭代一个大表而不会耗尽内存?
【发布时间】:2016-08-15 16:57:41
【问题描述】:

我有一个 Django 模型,它的表中有数百万条记录。我正在尝试在 shell 中对表中的所有记录进行一些紧急维护,但我无法在不完全耗尽系统内存的情况下执行 MyModel.objects.all()

即使是 pass 也会导致调用 OOM 杀手,从而杀死我的进程:

for ii in MyModel.objects.all():
    pass

原因是因为 Django 的 QuerySet 正在尝试建立它的“结果缓存”,方法是建立一个列表,其中包含 所有 我的记录,这里:

# django/db/models/query.py
def _fetch_all(self):
    if self._result_cache is None:
        self._result_cache = list(self.iterator())  # <<<< this guy!
    if self._prefetch_related_lookups and not self._prefetch_done:
        self._prefetch_related_objects()

但我的机器无法将整个列表保存在内存中。

当然,在如此大的表上迭代 .all() 在实际应用中是一个糟糕的想法,因此这个问题的范围相当有限(维护活动),但它确实会不时出现。

【问题讨论】:

    标签: python django


    【解决方案1】:

    首先要尝试在查询集上使用iterator() 方法,然后再对其进行迭代:

    for ii in MyModel.objects.all().iterator():
    

    【讨论】:

    • 嗯,这似乎绕过了结果缓存,但我仍然内存不足(在django/db/backends/utils.py 中的CursorWrapper.execute 中)...所以我猜结果缓存不是唯一让我丧命的,就是从 postgres 中获取的大量记录......
    • 这让我很吃惊,但我还没有完全深入了解 Django 使用游标的位置与将记录拉入内存的位置。如果您可以在没有实际模型实例的情况下进行维护或愿意将原始数据库记录转换为模型,那么从这个答案链接的博客文章之一可能会有所帮助? stackoverflow.com/questions/18381695/…docs.djangoproject.com/en/1.10/topics/db/sql/… 用于 Django 文档。
    • 我在模型实例上调用方法,但我完全可以考虑到这一点,这是我希望避免的一些工作。对于这里和那里的奇怪维护来说,这并不是什么大不了的事。我只是希望有一种很好的方法可以做到这一点,而不会下降到原始数据库记录。哦,好吧。
    • 想一想,您可能可以使用重复切片来执行此操作 - 查询集切片在底层 sql 查询中实现为偏移/限制参数,因此 MyModel.objects.all()[0:100] 真的只获取 100 条记录,然后使用新查询获取MyModel.objects.all()[100:200] 等。显然,这有点痛苦,但可能不如重新设计模型方法那么糟糕。这就是 Django 分页的工作方式,因此它可能有助于抽象:docs.djangoproject.com/en/1.10/topics/pagination
    • 如果您使用切片/分页,明显的警告是您不能在维护中删除或添加记录,或者进行会影响排序顺序的更改,因为它每次都会重新评估查询。
    【解决方案2】:

    正确的答案是在迭代之前在查询集上使用 Django 的iterator() 方法。但是,您还必须将查询包装在事务中。

        with transaction.atomic():
            for ii in MyModel.objects.all().iterator():
    

    这是因为默认情况下 Django 以“自动提交”模式运行,这意味着数据库游标将具有 WITH HOLD 参数,导致常见的数据库(如 postgres)使用大型临时文件。

    【讨论】:

      【解决方案3】:

      如果你使用的是 python3.X,你可以尝试一些异步任务。

      创建计划提取可能很有用。

      类似这样的:

      async def _fetch_all(self):
           if self._result_cache is None:
                self._result_cache = await list(self.iterator())  # <<<< this guy!
           if self._prefetch_related_lookups and not self._prefetch_done:
                await self._prefetch_related_objects()
      

      运行您的代码:

      import asyncio
      my_model = MyModel()
      asyncio.get_event_loop().run_until_complete(my_model._fetch_all())
      

      但如果您使用的是 2.7,则需要使用 celery 创建异步任务或尝试使用一些工具来执行此操作,例如 Django Async

      希望对你有帮助。

      Take a look

      【讨论】:

        猜你喜欢
        • 2016-12-25
        • 2021-02-10
        • 1970-01-01
        • 1970-01-01
        • 2020-11-20
        • 2018-10-17
        • 1970-01-01
        • 1970-01-01
        • 2019-12-26
        相关资源
        最近更新 更多