【问题标题】:What's the most efficient way to insert thousands of records into a table (MySQL, Python, Django)将数千条记录插入表中的最有效方法是什么(MySQL,Python,Django)
【发布时间】:2010-10-25 09:46:45
【问题描述】:

我有一个带有唯一字符串字段和几个整数字段的数据库表。字符串字段的长度通常为 10-100 个字符。

每分钟左右一次我有以下情况:我收到一个与表的记录结构相对应的 2-10,000 个元组的列表,例如

[("hello", 3, 4), ("cat", 5, 3), ...]

我需要将所有这些元组插入到表中(假设我验证了这些字符串都没有出现在数据库中)。为了澄清起见,我使用的是 InnoDB,并且我有一个用于该表的自动增量主键,该字符串不是 PK。

我的代码当前遍历这个列表,为每个元组创建一个具有适当值的 Python 模块对象,并调用“.save()”,如下所示:

@transaction.commit_on_success
def save_data_elements(input_list):
    for (s, i1, i2) in input_list:
        entry = DataElement(string=s, number1=i1, number2=i2)
        entry.save()

此代码目前是我系统中的性能瓶颈之一,因此我正在寻找优化它的方法。

例如,我可以生成每个包含 100 个元组的 INSERT 命令的 SQL 代码(“硬编码”到 SQL 中)并执行它,但我不知道它是否会改进。

您对优化这样的流程有什么建议吗?

谢谢

【问题讨论】:

  • 好问题!那么,最好的答案似乎是创建一个文本文件,或者通过字符串连接生成一个 SQL 查询?这有点不满意!

标签: python sql mysql django insert


【解决方案1】:

您可以将行写入格式为 "field1", "field2", .. 然后使用 LOAD DATA 来加载它们

data = '\n'.join(','.join('"%s"' % field for field in row) for row in data)
f= open('data.txt', 'w')
f.write(data)
f.close()

然后执行这个:

LOAD DATA INFILE 'data.txt' INTO TABLE db2.my_table;

Reference

【讨论】:

  • 除非代码在数据库服务器上运行,否则它需要是 LOAD DATA LOCAL INFILE。
  • 另外,在加载之前禁用索引,然后再启用它们(构建索引需要一段时间)。还没有查看它是否也有助于 Django 插入。
【解决方案2】:

特别是对于 MySQL,加载数据的最快方式是使用LOAD DATA INFILE,因此如果您可以将数据转换为预期的格式,这可能是将其放入表中的最快方式。

【讨论】:

  • 唯一的潜在问题是覆盖 save() 方法。如果你这样做,你将不得不对你的设计三思而后行。
  • @S.Lott:“覆盖 save()”是什么意思?您的意思是我是否覆盖了模块类中的 .save() 方法,以便在通过“加载数据文件”中丢失的代码进行保存时进行预处理/后处理任务?如果是这样 - 情况并非如此,我不会覆盖 .save()。否则请详细说明...谢谢
【解决方案3】:

如果您不 LOAD DATA INFILE 就像其他一些建议提到的那样,您可以做两件事来加快插入速度:

  1. 使用准备好的语句 - 这减少了每次插入时解析 SQL 的开销
  2. 在单个事务中执行所有插入 - 这需要使用支持事务的数据库引擎(如 InnoDB)

【讨论】:

  • @Sean:谢谢,“准备好的语句”是指带有许多 %s 元素的 SQL 代码,我只是通过提供字符串/数字列表来“填充”这些元素?另外,请查看我的代码(在问题的正文中) - 如果我理解正确,我已经在使用带有 @transaction.commit_on_success 装饰器的单个事务(我正在使用 InnoDB)
  • 我不太确定 Django 的幕后发生了什么——我只是来自使用 MySQL 的一般背景,所以我不知道它在事务方面做了什么。至于准备好的语句-看起来这是您的 DataElement 对象的实现细节。准备好的语句是:stmt = Prepare(sqlStatement); stmt.execute(var1, var2..) 而不是 db.execute(sqlStatement, var1, var2...) - 这就像编译正则表达式而不是每次都解析它们。
【解决方案4】:

如果您可以手动编写INSERT 声明,那么这就是我要走的路。包含多个值子句的单个 INSERT 语句比许多单独的 INSERT 语句快得多。

【讨论】:

  • @staticsan:您认为这样的声明有任何“实际”限制吗?即我可以向数据库发送一个包含 10k 行文本的单个 INSERT 查询吗?
  • 唯一真正的限制是网络缓冲区的大小。这个默认值很多年都是1Mb,但是很多人把它提升到最大16Mb。更新版本的 MySQL 甚至可以支持更大的数据包大小。
  • 更多的是数据包的大小而不是记录的数量。在构建插入缓冲区时,如果这样做会使缓冲区超过最大 mysql 数据包大小,请不要再添加。我会做一些基准测试,看看收益从哪里开始趋于平稳。您还可以询问您的 MySQL 服务器的最大数据包大小: mysql> select @@max_allowed_pa​​cket\G: @@max_allowed_pa​​cket: 33554432
【解决方案5】:

无论插入方法如何,您都希望使用 InnoDB 引擎来实现最大的读/写并发。 MyISAM 将在插入期间锁定整个表,而 InnoDB(在大多数情况下)只会锁定受影响的行,从而允许 SELECT 语句继续进行。

【讨论】:

  • 谢谢,我添加了一个说明,说明我正在使用 InnoDB
【解决方案6】:

你收到什么格式的?如果是文件,您可以进行某种批量加载:http://www.classes.cs.uchicago.edu/archive/2005/fall/23500-1/mysql-load.html

【讨论】:

    【解决方案7】:

    这与将数据实际加载到数据库中无关,但是...

    如果向用户提供“数据正在加载...加载将很快完成”类型的消息是一个选项,那么您可以在不同的线程中异步运行 INSERT 或 LOAD DATA。

    只是需要考虑的其他事情。

    【讨论】:

    • 更可能的问题是服务器正忙于处理此输入而无法处理任何其他请求。
    • 我已经在一个单独的线程中进行处理(用户不等待这个完成),我的问题是有时系统太忙所以队列有可能会填满比它的清理快足够的时间......
    【解决方案8】:

    我不知道确切的细节,但你可以使用 json 样式的数据表示并将其用作固定装置或其他东西。我在 Douglas Napoleone 的 Django Video Workshop 上看到了类似的东西。请参阅http://www.linux-magazine.com/online/news/django_video_workshop 上的视频。和http://www.linux-magazine.com/online/features/django_reloaded_workshop_part_1。希望这个对您有所帮助。

    希望你能解决。我刚开始学习 django,所以我可以为您指出资源。

    【讨论】:

      猜你喜欢
      • 2010-11-11
      • 2012-10-19
      • 2015-04-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-23
      相关资源
      最近更新 更多