【问题标题】:Django: Bulk operationsDjango:批量操作
【发布时间】:2012-12-11 16:38:38
【问题描述】:

业务:
我遇到了一个问题 - 当使用 Django ORM 处理大型数据集时,规范的方式是使用每个元素进行操作。但是当然这种方式是非常低效的。所以我决定使用原始 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.table\n"
               "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 很小:

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

也许 Django 不允许一次执行多个 SQL 查询?

ps 提交前关闭游标有助于避免异常,但这是否正确?

我的期望:
我正在寻找批量操作的所有可能的可靠解决方案(最好在 Django 内部)。 我不在乎它是 ORM 还是原始 SQL,如果我能避免错误,我会支持上面粘贴的代码。在没有解决方案的情况下,至少出于好奇,知道这个异常的原因是好的。

除了答案之外我学到了什么:
在 Django 1.4 中引入了bulk_create,用于高效的多个INSERT 操作

【问题讨论】:

  • 您是否尝试过在每个命令创建时执行,而不是创建一个列表然后执行?
  • @monkut 当然,但在这种情况下,Django 在执行每个元素时都会寻址到 DB,这会导致巨大速度变慢。
  • 您是在更新模型还是只是在更新未映射到模型的常规表?如果您正在尝试批量更新模型,可以尝试docs.djangoproject.com/en/dev/topics/db/queries/…
  • 另外,您可以使用 IN 子句来避免多次查询:tutorialspoint.com/mysql/mysql-in-clause.htm
  • @msc 指向使用多个值而不是相同值更新多个对象。回答您的问题 - 是的,它与模型有关。

标签: python sql django orm bulk


【解决方案1】:

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 语句中运行多个查询并不能真正提高运行速度。真正改进的是像上面那样同时对多行进行操作的单个查询。您可以在更新语句中构建相当复杂的公式,因此如果您的“produce_some_differentiated_data”方法可以以这种方式表达,则最好。即使不能直接完成,您也可以对模型进行一些修改并添加一些额外的字段来实现这一点。如果经常执行此类批量操作,这可能会有所回报。

来自 Django 的文档:

Django 支持使用加法、减法、乘法、 带有 F() 对象的除法和模运算,两者都带有常量 以及其他 F() 对象。

更多信息在这里: https://docs.djangoproject.com/en/dev/topics/db/queries/#updating-multiple-objects-at-once

【讨论】:

    【解决方案2】:

    如果您需要原始 SQL,请使用 cursor.executemany(query, param_list)

    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 注入攻击)。

    这是一个未记录的方法,尽管从 Django-0.96 到当前的 1.5-beta 很长时间以来,所有本机数据库后端(mysql、postgesql_psycopg2、sqlite3、oracle)都支持游标对象两种方法 execute(self, sql, params=())executemany(self, sql, param_list) .一个有用的类似答案是https://stackoverflow.com/a/6101536/448474

    executemany 方法在前几年修复了两个与异常处理相关的问题。因此,请验证您的 Django 版本,如果您故意导致数据库异常、太多 %s 或太少等,您会收到有用的错误消息。然而,几分钟的初始修补/测试比等待数小时的慢速要快方法。

    【讨论】:

      【解决方案3】:

      您是否尝试过交易。

      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 连接,避免如此多的调用并执行几行的几次提交。

      在上面的示例中,您可以将行分成两半、三分之一或十分之一等。

      【讨论】:

      • 即使是小块我也会得到异常。
      猜你喜欢
      • 1970-01-01
      • 2015-09-27
      • 1970-01-01
      • 2017-05-17
      • 1970-01-01
      • 1970-01-01
      • 2010-09-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多