使用Django的ORM加速批量插入?
我计划使用django的ORM将大约750个文件(每个〜250MB)的十亿条logging上传到一个数据库。 目前每个文件需要20分钟的时间来处理,我想知道是否有任何方法来加速这个过程。
我采取了以下措施:
- 每使用@ transaction.commit_manually并提交一次,每5000条logging
- 设置DEBUG = False,这样django 就不会在内存中累积所有的sql命令
- 循环遍历单个文件中的logging完全包含在单个函数中(最小化堆栈更改)
- 从敲击查询数据库(使用已经在db中的对象的本地散列而不是使用get_or_create )
- 在save()中设置force_insert = True,希望能够保存django的一些逻辑
- 显式设置id希望能够保存django的一些逻辑
- 通用代码最小化和优化
我还能做些什么来加快速度? 这是我的一些想法:
- 使用某种Python编译器或更快的版本(Psyco?)
- 重写ORM并直接使用SQL
- 使用一些可能更好的第三方代码( 1,2 )
- 请求django社区创build一个bulk_insert函数
任何有关这些项目或任何其他想法的指针将受到欢迎:)
Django 1.4在QuerySet对象上提供了一个bulk_create()
方法,请参阅:
这不是特定于Django的ORM,但最近我不得不从2000多个文件中将大于6千万行的8列数据插入sqlite3数据库。 而且我知道以下三件事将插入时间从48小时减less到〜1小时:
-
增加您的数据库的caching大小设置使用更多的RAM(默认的总是非常小,我用3GB); 在sqlite中,这是由PRAGMA cache_size = n_of_pages;
-
在RAM中而不是在磁盘上进行日志logging(如果系统出现故障,这会造成轻微的问题,但考虑到您已经在磁盘上有源数据,我认为这是微不足道的)。 在sqlite中这是由PRAGMA journal_mode = MEMORY完成的
-
最后也许是最重要的一点:插入时不要build立索引。 这也意味着不声明可能导致DBbuild立索引的UNIQUE或其他约束。 只有在完成插入后才能build立索引。
正如前面提到的,你也应该使用cursor.executemany()(或者只是快捷方式conn.executemany())。 要使用它,请执行:
cursor.executemany('INSERT INTO mytable (field1, field2, field3) VALUES (?, ?, ?)', iterable_data)
iterable_data可以是一个列表或类似的东西,甚至是一个开放的文件阅读器。
放到DB-API并使用cursor.executemany()
。 详情请参阅PEP 249 。
http://djangosnippets.org/snippets/446/还有一个批量插入片段。;
这给出了一个插入命令的多个值对(INSERT INTO x(val1,val2)VALUES(1,2),(3,4) – etc等)。 这应该会大大提高性能。
它也似乎是大量logging,这总是一个加号。
另外,如果你想快速简单的事情,你可以试试这个: http : //djangosnippets.org/snippets/2362/ 。 这是我在一个项目上使用的一个简单的经理。
另一个片段并不是那么简单,而是真正专注于关系的批量插入。 这只是一个普通的批量插入,只是使用相同的INSERT查询。
发展django得到bulk_create: https ://docs.djangoproject.com/en/dev/ref/models/querysets/#django.db.models.query.QuerySet.bulk_create
我在Django 1.10 / Postgresql 9.4 / Pandas 0.19.0上运行了一些testing,得到如下的时间:
- 单独插入3000行,并使用Django ORM: 3200ms从填充对象获取ID
- 用Pandas
DataFrame.to_sql()
插入3000行,不会得到ID: 774ms - 用Django manager
.bulk_create(Model(**df.to_records()))
插入3000行,不会得到ID: 574ms - 用
to_csv
插入3000行到StringIO
缓冲区并COPY
(cur.copy_from()
),不要得到ID: 118ms - 用
to_csv
和COPY
插入3000行,并通过简单的SELECT WHERE ID > [max ID before insert]
(可能不是线程安全,除非COPY
在表上阻止同时插入的locking)获得ID: 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()
性能统计数据全部在已经包含3000行OS X(i7 SSD 16GB)的表上获得,使用timeit
平均10次运行。
我通过分配一个导入的批次ID和主键sorting来得到我插入的主键,尽pipe我不是100%确定的主键将始终按照COPY
命令序列化的顺序进行分配 – 不pipe怎样, 。