Ответ 1
Django 1.4 предоставляет метод bulk_create()
для объекта QuerySet:
Я планирую загрузить миллиард записей, взятых из ~ 750 файлов (каждый ~ 250 МБ) в базу данных, используя django ORM. В настоящее время для обработки каждого файла требуется ~ 20 минут, и мне было интересно, есть ли способ ускорить этот процесс.
Я принял следующие меры:
Что еще я могу сделать, чтобы ускорить процесс? Вот некоторые из моих мыслей:
Любые указатели относительно этих предметов или любые другие идеи будут приветствоваться :)
Django 1.4 предоставляет метод bulk_create()
для объекта QuerySet:
Это не относится к Django ORM, но в последнее время мне приходилось массировать вставки > 60 миллионов строк из 8 столбцов данных из более чем 2000 файлов в базу данных sqlite3. И я узнал, что следующие три вещи сократили время вставки с 48 часов до ~ 1 часа:
увеличить размер кеш-памяти вашей БД, чтобы использовать больше ОЗУ (по умолчанию они всегда очень маленький, я использовал 3 ГБ); в sqlite это делается PRAGMA cache_size = n_of_pages;
записывать в RAM вместо диска (это вызывает незначительные проблема, если система выходит из строя, но что-то, что я считаю пренебрежимым если у вас уже есть исходные данные на диске); в sqlite это делается PRAGMA journal_mode = MEMORY
последний и, возможно, самый важный: не создавайте индекс, пока вставка. Это также означает, что нельзя объявлять UNIQUE или другие ограничения, которые могут привести к созданию индекса DB. Создайте индекс только после того, как вы закончите вставку.
Как упоминалось ранее, вы также должны использовать cursor.executemany() (или просто ярлык conn.executemany()). Чтобы использовать его, выполните:
cursor.executemany('INSERT INTO mytable (field1, field2, field3) VALUES (?, ?, ?)', iterable_data)
Итерируемые_данные могут быть списком или чем-то похожим, или даже читателем открытого файла.
Отбросьте DB-API и используйте cursor.executemany()
. Подробнее см. PEP 249.
Я провел несколько тестов на Django 1.10/Postgresql 9.4/Pandas 0.19.0 и получил следующие тайминги:
DataFrame.to_sql()
и не получите идентификаторы: 774ms df.to_sql()
быстрее благодаря необязательному объединению нескольких вставок в один оператор - я его не проверял.bulk_create(Model(**df.to_records()))
и не получите идентификаторы: 574msto_csv
в буфер StringIO
и COPY
(cur.copy_from()
) и не получите идентификаторы: 118msto_csv
и COPY
и получите идентификаторы с помощью простого SELECT WHERE ID > [max ID before insert]
(возможно, не является поточно-ориентированным, если COPY
удерживает блокировку таблицы, предотвращая одновременные вставки?): 201 мс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 16 ГБ), в среднем из десяти запусков с использованием timeit
.
Я возвращаю свои вставленные первичные ключи, назначая идентификатор пакета импорта и сортируя по первичному ключу, хотя я не на 100% уверен, что первичные ключи всегда будут назначаться в том порядке, в котором строки сериализуются для команды COPY
- буду признателен за мнения в любом случае,
Также есть фрагмент объемной вставки http://djangosnippets.org/snippets/446/.
Это дает одну команду вставки нескольких пар значений (INSERT INTO x (val1, val2) VALUES (1,2), (3,4) --etc и т.д.). Это должно значительно повысить производительность.
Он также сильно документирован, что всегда является плюсом.
Кроме того, если вам нужно что-то быстрое и простое, вы можете попробовать следующее: 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