首页 > 解决方案 > 从 Django ORM 中高效提取大量数据

问题描述

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

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

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

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

额外信息:

编辑:使用psycopg2服务器端游标似乎可行,但速度较慢且丑陋:如何将服务器端游标与 django 和 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 模型,注意我在 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",)

标签: pythondjangopostgresqlpsycopg2

解决方案


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

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

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

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


推荐阅读