Ускорить массовую вставку с помощью ORM Джанго? - PullRequest
43 голосов
/ 28 ноября 2010

Я планирую загрузить миллиард записей, взятых из ~ 750 файлов (каждый ~ 250 МБ) в базу данных, используя ORM django. В настоящее время обработка каждого файла занимает ~ 20 минут, и мне было интересно, есть ли способ ускорить этот процесс.

Я принял следующие меры:

Что еще я могу сделать, чтобы ускорить процесс? Вот некоторые из моих мыслей:

Любые указатели относительно этих предметов или любые другие идеи приветствуются:)

Ответы [ 7 ]

33 голосов
/ 14 февраля 2012
16 голосов
/ 26 сентября 2012

Это не относится к Django ORM, но недавно мне пришлось массово вставить> 60 миллионов строк из 8 столбцов данных из более чем 2000 файлов в базу данных sqlite3. И я узнал, что следующие три вещи сократили время вставки с более чем 48 часов до ~ 1 часа:

  1. увеличить размер кэша вашей БД, чтобы использовать больше оперативной памяти (по умолчанию всегда очень маленький, я использовал 3гб); в sqlite это делается PRAGMA cache_size = n_of_pages;

  2. делать журналирование в оперативной памяти вместо диска (это вызывает небольшое проблема, если система выходит из строя, но что-то я считаю незначительным учитывая, что у вас уже есть исходные данные на диске); в sqlite это делается PRAGMA journal_mode = MEMORY

  3. последний и, возможно, самый важный: не создавайте индекс, пока вставка. Это также означает, что нельзя объявлять UNIQUE или другие ограничения, которые могут привести к тому, что БД создаст индекс. Построить индекс только после того, как вы закончите вставку.

Как уже упоминалось ранее, вы также должны использовать cursor.executemany () (или просто ярлык conn.executemany ()). Чтобы использовать это, сделайте:

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

Итерируемые_данные могут быть списком или чем-то в этом роде, или даже программой чтения открытых файлов.

12 голосов
/ 28 ноября 2010

Перейдите в DB-API и используйте cursor.executemany(). Подробнее см. PEP 249 .

7 голосов
/ 21 июля 2017

Я провел несколько тестов на Django 1.10 / Postgresql 9.4 / Pandas 0.19.0 и получил следующие тайминги:

  • Вставьте 3000 строк по отдельности и получите идентификаторы из заполненных объектов, используя Django ORM: 3200ms
  • Вставьте 3000 строк с пандами DataFrame.to_sql() и не получите идентификаторы: 774ms
  • Вставьте 3000 строк с помощью менеджера Django .bulk_create(Model(**df.to_records())) и не получите идентификаторы: 574ms
  • Вставьте 3000 строк с буфером to_csv в StringIO и COPY (cur.copy_from()) и не получите идентификаторы: 118ms
  • Вставьте 3000 строк с to_csv и COPY и получите идентификаторы с помощью простого SELECT WHERE ID > [max ID before insert] (возможно, не поточно-ориентированный, если 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()

Статистика производительности была получена для таблицы, уже содержащей 3000 строк, работающих на OS X (i7 SSD 16 ГБ), в среднем из десяти запусков с использованием timeit.

Я возвращаю мои вставленные первичные ключи, назначая идентификатор пакета импорта и сортируя по первичному ключу, хотя я не на 100% уверен, что первичные ключи всегда будут назначаться в том порядке, в котором строки сериализуются для команды COPY - был бы признателен за мнения в любом случае.

5 голосов
/ 10 февраля 2011

Существует также фрагмент вставки массива в http://djangosnippets.org/snippets/446/.

Это дает одной команде вставки несколько пар значений (INSERT INTO x (val1, val2) VALUES (1,2), (3,4) - и т. Д.). Это должно значительно улучшить производительность.

Похоже, что документация тщательно документирована, что всегда является плюсом.

3 голосов
/ 20 октября 2011
3 голосов
/ 19 февраля 2011

Кроме того, если вы хотите что-то быстрое и простое, вы можете попробовать это: http://djangosnippets.org/snippets/2362/. Это простой менеджер, который я использовал в проекте.

Другой фрагмент не был таким простым и был действительно сосредоточен на массовых вставках для отношений. Это обычная массовая вставка, в которой используется тот же запрос INSERT.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...