使用 Django 的 ORM 加速批量插入?

Posted

技术标签:

【中文标题】使用 Django 的 ORM 加速批量插入?【英文标题】:Accelerate bulk insert using Django's ORM? 【发布时间】:2011-05-16 16:44:17 【问题描述】:

我计划使用 django 的 ORM 将从 ~750 个文件(每个 ~250MB)中提取的 10 亿条记录上传到数据库。 目前每个文件需要大约 20 分钟来处理,我想知道是否有任何方法可以加速这个过程。

我采取了以下措施:

Use @transaction.commit_manually 并每 5000 条记录提交一次 设置 DEBUG=False 以便 django won't accumulate all the sql commands in memory 遍历单个文件中记录的循环完全包含在单个函数中(最小化堆栈更改) 避免访问数据库进行查询(使用数据库中已有对象的本地哈希instead of using get_or_create) Set force_insert=True in the save() 希望它能为 django 节省一些逻辑 Explicitly set the id 希望它能为 django 节省一些逻辑 一般代码最小化和优化

我还能做些什么来加快速度?以下是我的一些想法:

使用某种更快的 Python 编译器或版本(Psyco?) Override the ORM and use SQL directly 使用一些可能更好的第 3 方代码(1、2) Beg the django community 创建一个 bulk_insert 函数

欢迎任何关于这些项目或任何其他想法的指针:)

【问题讨论】:

你也可以看看像 Pentaho Kettle 这样的 ETL 工具。 优化 python 的东西几乎肯定是一种浪费,因为你几乎所有的时间都花在了数据库调用上。优化 101,在您浪费时间尝试优化错误的事情之前,测量以了解您的程序时间的去向。最大的收获是使用批量插入查询。 我最近用 django 1.8.5 做了一些有趣的实验。我认为创建模型是最耗时的事情,当记录数达到 100 万时。幕后有许多看不见的 django 检查。我的解决方案是使用原始 SQL 和 cursor.executemany 而不是 bulk_create。就我而言,时间从 13 分钟缩短到 54 秒。 ***.com/questions/32805766/… @stanleyxu2005 真的吗?我虽然 Django 没有检查 bulk_create。这怎么可能,任何 Django 专家? 【参考方案1】:

Django 1.4 在 QuerySet 对象上提供了一个bulk_create() 方法,参见:

https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.bulk_create https://docs.djangoproject.com/en/dev/releases/1.4/ https://code.djangoproject.com/ticket/7596

【讨论】:

除非你需要自动主键。如果您使用原始查询,至少在某些数据库(mysql、Postgres)上是可行的。 自动主键现在至少从 postgresql 1.11 开始可用。您正在考虑使用 bulk_create 来提高性能的附加说明:code.djangoproject.com/ticket/28231【参考方案2】:

这不是 Django ORM 特有的,但最近我不得不将 2000 多个文件的 8 列数据的 6000 万行大容量插入到 sqlite3 数据库中。我了解到以下三件事将插入时间从超过 48 小时减少到了约 1 小时:

    增加数据库的缓存大小设置以使用更多 RAM(默认的总是非常 小,我用了 3GB);在 sqlite 中,这是由 PRAGMA cache_size = n_of_pages;

    在 RAM 而不是磁盘中进行日志记录(这确实会导致轻微的 如果系统发生故障会出现问题,但我认为可以忽略不计 假设您已经在磁盘上有源数据);在 sqlite 中,这是由 PRAGMA journal_mode = MEMORY

    完成的

    最后也是最重要的一个:不要在 插入。这也意味着不声明 UNIQUE 或其他可能导致数据库构建索引的约束。仅在完成插入后建立索引。

正如之前有人提到的,您还应该使用 cursor.executemany()(或者只是快捷方式 conn.executemany())。要使用它,请执行以下操作:

cursor.executemany('INSERT INTO mytable (field1, field2, field3) VALUES (?, ?, ?)', iterable_data)

iterable_data 可以是一个列表或类似的东西,甚至是一个打开的文件阅读器。

【讨论】:

你用过peewee ORM吗?我在批量插入时遇到性能问题 我添加了一个关于如何在 Django ***.com/questions/61270291/… 中执行 #3 的问题。 #1 和 #2 也帮助了我很多,谢谢!【参考方案3】:

Drop to DB-API 并使用 cursor.executemany()。详情请见PEP 249。

【讨论】:

我找不到任何文档\示例如何使用 executemany() 所以我使用了 execute() 但每次我准备一个插入 3000 条记录的 sql 语句时。这加快了速度。谢谢 文档在 PEP 249 中。 如果你想要cursor.executemany()的例子,在这个另一个answer中有一个【参考方案4】:

我在 Django 1.10 / Postgresql 9.4 / Pandas 0.19.0 上运行了一些测试,得到了以下时间:

单独插入 3000 行并使用 Django ORM 从填充对象中获取 ID:3200ms 用 Pandas DataFrame.to_sql() 插入 3000 行并且没有得到 ID:774ms 使用 Django 管理器 .bulk_create(Model(**df.to_records())) 插入 3000 行并且没有得到 ID:574ms 使用to_csvStringIO 缓冲区和COPY (cur.copy_from()) 插入3000 行并且不获取ID:118ms 使用to_csvCOPY 插入3000 行并通过简单的SELECT WHERE ID > [max ID before insert] 获取ID(可能不是线程安全的,除非COPY 持有表上的锁以防止同时插入?):201ms
def bulk_to_sql(df, columns, model_cls):
    """ Inserting 3000 takes 774ms avg """
    engine = ExcelImportProcessor._get_sqlalchemy_engine()
    df[columns].to_sql(model_cls._meta.db_table, con=engine, if_exists='append', index=False)


def bulk_via_csv(df, columns, model_cls):
    """ Inserting 3000 takes 118ms avg """
    engine = ExcelImportProcessor._get_sqlalchemy_engine()
    connection = engine.raw_connection()
    cursor = connection.cursor()
    output = StringIO()
    df[columns].to_csv(output, sep='\t', header=False, index=False)
    output.seek(0)
    contents = output.getvalue()
    cur = connection.cursor()
    cur.copy_from(output, model_cls._meta.db_table, null="", columns=columns)
    connection.commit()
    cur.close()

性能统计数据均在已包含 3,000 行的 OS X(i7 SSD 16GB)上运行的表上获得,平均使用 timeit 运行十次。

我通过分配导入批处理 ID 并按主键排序来取回插入的主键,尽管我不是 100% 某些主键将始终按照 COPY 命令的行序列化顺序分配 -无论哪种方式都将不胜感激。

2020 年更新:

我在 Pandas >= 0.24 中测试了新的 to_sql(method="multi") 功能,它将所有插入放入单个多行插入语句中。令人惊讶的是,无论是 Pandas 0.23、0.24 还是 1.1 版本,性能都。 Pandas 单行插入也比直接向数据库发出的多行插入语句要快。这次我在更大的数据库中使用更复杂的数据,但 to_csvcursor.copy_from 仍然比最快的替代方案快 38% 左右,后者是单行 df.to_sqlbulk_import 偶尔可比较,但通常仍然更慢(最多两倍的时间,Django 2.2)。

【讨论】:

【参考方案5】:

http://djangosnippets.org/snippets/446/ 上还有一个批量插入 sn-p。

这为一个插入命令提供了多个值对(INSERT INTO x (val1, val2) VALUES (1,2), (3,4) --etc 等)。这应该会大大提高性能。

它似乎也有大量记录,这总是一个优点。

【讨论】:

【参考方案6】:

另外,如果你想要快速简单的东西,你可以试试这个:http://djangosnippets.org/snippets/2362/。这是我在项目中使用的一个简单的管理器。

另一个 sn-p 并不那么简单,而是真正专注于为关系批量插入。这只是一个普通的批量插入,只使用相同的 INSERT 查询。

【讨论】:

【参考方案7】:

开发django得到bulk_create:https://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.bulk_create

【讨论】:

以上是关于使用 Django 的 ORM 加速批量插入?的主要内容,如果未能解决你的问题,请参考以下文章

django 导入Excel文件 ORM 批量操作

Django ORM批量操作和foreign key

任何为高速批量插入提供体面方法的 ORM?

Django开发之ORM批量操作

Django开发之ORM批量操作

python ORM允许创建表和批量插入?