Django: массовые операции

бизнес:
Я столкнулся с проблемой-при работе с большими наборами данных с Django ORM канонический способ манипулирует каждым отдельным элементом. Но, конечно, этот способ очень неэффективен. Поэтому я решил использовать raw SQL.

содержание:
У меня есть базовый код, который формирует SQL-запрос, который обновляет строки таблицы и фиксирует его:

from myapp import Model
from django.db import connection, transaction
COUNT = Model.objects.count()
MYDATA = produce_some_differentiated_data() #Creating individual value for each row
cursor = connection.cursor()
str = []
for i in xrange(1, COUNT):
    str.append("UPDATE database.tablen"
               "SET field_to_modify={}n"
               "WHERE primary_key_field={};n".format(MYDATA, i))


str = ''.join(str)
cursor.execute(str)
transaction.commit_unless_managed() #This cause exception

и на последнем заявлении я получаю это, даже когда SIZE is малый:

_mysql_exceptions.ProgrammingError: (2014, "Commands out of sync; you can't run this command now")

может быть, Django не позволяет выполнять сразу несколько SQL-запросов?

ps Закрытие курсора перед фиксацией помогает избежать исключения, но правильно ли это?

мои ожидания:
Im ищет все возможные твердые решения для массовых операций (предпочтительно внутри Django). Мне все равно, будет ли это ORM или raw SQL, я бы встал с кодом, который я вставил выше, если бы мог избежать ошибки. В случае отсутствия решений это будет хорошо хотя бы из любопытства узнать причину этого исключения.

то, что я узнал, помимо ответов:
В Django 1.4 был представлен bulk_create, для эффективной несколько INSERT операции

3 ответов


использовать cursor.executemany(query, param_list) если вам нужен чистый SQL.

param_list = [("something_1", 1), ("something_2", 2),...]
# or everything like [(some_number_1, 1)...]. Apostrophes around the substituted
# "%s" and the complete necessary escaping is added automatically for string
# parameters.

cursor.executemany("""UPDATE database.table
        SET field_to_modify=%s
        WHERE primary_key_field=%s""",
        param_list)

Он имеет много преимуществ:

  • строка запроса намного короче большого запроса и может быть быстро проанализирована/оптимизирована без использования ненужных ресурсов планировщиком базы данных. (При разборе параметров в SQL вручную, вы получаете много различных команд SQL, которые должны быть проанализированы по отдельности.)
  • разбор строк в SQL вручную-плохая практика, потому что это может быть проблема безопасности (атака SQL-инъекции), если вы не избегаете неожиданных апострофов и обратных косых черт из пользовательского ввода правильно.

это недокументированный метод, хотя оба метода execute(self, sql, params=()) и executemany(self, sql, param_list) поддерживаются для объектов курсора всеми собственными бэкэндами БД (mysql, postgesql_psycopg2, sqlite3, oracle) в течение длительного времени с Django-0.96 до текущей 1.5-beta. Полезным подобный ответ https://stackoverflow.com/a/6101536/448474 .

в метод executemany имел две фиксированные проблемы, связанные с обработкой исключений в предыдущие годы. Итак, проверьте для своей версии Django, что вы получаете полезные сообщения об ошибках, если вы намеренно вызываете исключение базы данных, слишком много %s или слишком мало и т. д. Тем не менее, несколько минут начальной обработки/тестирования быстрее, чем многие часы ожидания медленных методов.


Django 1.4+ имеет довольно приличную поддержку для массовых операций в его ORM, и вы должны увидеть, можете ли вы использовать это - это самый портативный способ и довольно приятно работать.

это позволяет не только обновлять одно и то же значение для поля во всех объектах (это тривиально), но и обновлять значения полей на основе других полей, а также выполнять некоторые ограниченные вычисления. Я не уверен, что это соответствует вашим потребностям (зависит от того, как работает "produce_some_differentiated_data") - некоторые из расчеты можно сделать, некоторые из них, вероятно, нет. Пример:

image_id_list = [1,5,6]
Image.objects.filter(image_id__in=image_id_list).
     update(views_number=F('views_number') + 1)

приведенный выше пример преобразуется в SQL аналогично:

UPDATE image SET views_number = views_number + 1 WHERE image_id IN (1,5,6);

что является самым быстрым способом выполнения массового обновления - быстрее, чем выполнение нескольких запросов. Выполнение нескольких запросов в одной инструкции SQL на самом деле не улучшает скорость работы. Что действительно улучшает его, так это сделать один запрос, подобный приведенному выше, который работает на многих строках одновременно. Вы можете построить довольно сложный формулы в инструкции update, поэтому лучше всего, если ваш метод" produce_some_differentiated_data " может быть выражен таким образом. Даже если это невозможно сделать напрямую, вы можете внести некоторые изменения в модель и добавить дополнительные поля, чтобы это произошло. Это может окупиться, если такие массовые операции выполняются часто.

из документации Django:

Django поддерживает использование сложения, вычитания, умножения, деление и арифметика по модулю с объектами F (), как с константами и с другими объектами F ().

подробнее об этом здесь: https://docs.djangoproject.com/en/dev/topics/db/queries/#updating-multiple-objects-at-once


вы пробовали сделок.

https://docs.djangoproject.com/en/dev/topics/db/transactions/

вам нужно что-то вроде этого.

@transaction.commit_manually
def viewfunc(request):
    for row in rows:
        row.modify() # wherever you want to change
    transaction.commit()

проблема не в накладных расходах ORM, это соединение db, избегайте так много вызовов и выполните несколько коммитов из нескольких строк каждый.

в приведенном выше примере вы можете разделить строки пополам,или треть или десятую и т. д..