【问题标题】:Efficiently extract large amounts of data from Django ORM从 Django ORM 中高效提取大量数据
【发布时间】:2021-08-05 05:14:56
【问题描述】:

我有一个带有 PostgreSQL DB 的 Django 设置。 其中一个表包含大量数据(>1e9 行),我需要有效地迭代其中的一个大子集。

目前,当我尝试选择大量数据时,它开始缓冲结果并且我的计算机内存不足。 如果我在 QuerySet 上使用 .iterator(),它就会挂起。 如果我尝试使用原始 SQL 和 fetchall(),它也会开始缓冲。

我相信 Django 将 psycopg2 用于具有 cursor.itersize 参数的 PostgreSQL,但是当我尝试在 Django 中将它与 cursor 一起使用时,它什么也没做。

我知道问题不在数据库端,因为我可以使用psql(使用-A --variable="FETCH_COUNT=10000")执行查询,并且它会立即开始加载而不使用任何内存。

额外信息:

  • 该表有 >10 列,但我只需要其中 2 列,因此如果可以仅获取选定的以加快加载速度,那就太好了。

编辑:使用psycopg2 服务器端光标似乎可以工作,但速度较慢且丑陋:How can I use server-side cursors with django and psycopg2?

编辑 2:这是现在为我工作的代码,但非常难看:

def get_stuff():
    def fetch_from_server_cursor(cursor, cursor_name, fetch_size=10_000):
        while True:
            cursor.execute(f"FETCH {fetch_size} FROM {cursor_name}")
            chunk = cursor.fetchall()
            if not chunk:
                return
            yield from chunk

    with transaction.atomic(), connection.cursor() as cursor:
        cursor_name = "my_cursor"
        cursor.execute(
            f"""
            DECLARE {cursor_name} CURSOR FOR
            SELECT first_column, second_column
            FROM {MyModel.objects.model._meta.db_table}
            """
        )
        yield from fetch_from_server_cursor(cursor, cursor_name)

编辑 3:这是 Django 模型,N.B.我在 DB 的表上使用 Timescale,它会自动在 TimeScaleDateTimeField 上创建索引:

class MyModel(models.Model):
    first_column = models.IntegerField()
    second_column = models.TimeScaleDateTimeField()
    third_column = models.URLField(null=True, blank=True)
    ...

    class Meta:
        ordering = ("second_column",)

【问题讨论】:

  • 它挂起的原因是因为Python在处理1e9项时非常慢。所以迭代器确实会将它分成块,但很可能你会迭代接收到的数据,而 Python 由于其动态特性,在这方面非常慢。
  • 这就是我要问的,我如何让它遍历数据,类似于 python 生成器,而不是试图一次获取所有数据。
  • 对于.iterator(),它不会“挂起”,因为最终迭代将结束,Python 处理所有这些元素需要花费很多时间。
  • 参见例如sum(range(1, 1000000000)),计算总和需要几分钟,range 也是一个迭代器。现在对于 Django,它甚至会花费 更多 时间,因为它将数据包装在模型对象等中。
  • @Rizhiy 请在您的问题中添加minimal reproducible example(即您已编写或正在尝试运行的代码),当您的问题与代码有关时,有时代码胜过文字;)

标签: python django postgresql psycopg2


【解决方案1】:

该表有 [多于] 10 列,但我只需要其中的 2 列,因此如果可以仅获取选定的以更快加载,那就太好了。

您可以使用.only(…) [Django-doc] 执行此操作,以仅选择列的子集,例如:

for item in MyModel.objects.only('pk', 'other_column').iterator():
    print((item.pk, item.other_column))

这将减少数据库和应用层之间的一些带宽。但无论如何,109 项在 Python 中通常是不可行的。例如,如果我们简单地总结这样范围的项目 (sum(range(1000000000))),它将花费 ≈ 14 秒,但这是一个非常简单的生成器。 Django 将为每条记录从数据库中读取内容,创建一个模型对象,并相应地设置字段,因此这很容易花费几分钟甚至几小时。

【讨论】:

  • 这并不能解决缓冲的主要问题。
  • 花很长时间不是问题。
  • @Rizhiy:你可以在iterator中设置chunk_size.only只会热切地抓取这两列。
  • 现在试了一下,没有缓冲,但还是挂了(
  • 添加了在问题编辑中对我有用的代码。
猜你喜欢
  • 2020-02-21
  • 1970-01-01
  • 2018-08-18
  • 1970-01-01
  • 1970-01-01
  • 2017-10-10
  • 1970-01-01
  • 2020-05-31
  • 2020-09-12
相关资源
最近更新 更多