将记录批量插入 Active Record 表

Posted

技术标签:

【中文标题】将记录批量插入 Active Record 表【英文标题】:Bulk Insert records into Active Record table 【发布时间】:2013-02-25 09:42:57 【问题描述】:

当我一次添加大量记录时,我发现我的 Model.create! 语句需要很长时间才能运行。查看ActiveRecord-Import,但它不适用于哈希数组(这是我所拥有的,我认为这很常见)。如何提高性能?

【问题讨论】:

另见:How to implement bulk insert in Rails 3 和 Batch insertion in rails 3。 @Cupcake 这两篇参考文献谈到了使用 ActiveRecord-Import(它不支持像 create! 那样的哈希数组)和直接编写 SQL。请参阅我所做的已接受的答案。 这个 gem github.com/bjhaid/active_record_bulk_insert 旨在解决这种挑战 可能有帮助cbabhusal.wordpress.com/2015/01/03/… @illusionist 那篇文章讨论了一个非常具体的批量更新:当所有记录都用相同的值更新时。对于具有多个值的多条记录更新,最佳方法类似于批量插入所采用的方法(请参阅我在 ***.com/questions/18999563/… 上的回答) 【参考方案1】:

感谢 Chris Heald @cheald 的 2009 article,向我展示了最好的方法是多行插入命令。

将以下代码添加到我的initializers/active_record.rb 文件中,将我的Model.create!(...) 调用更改为Model.import!(...),然后它就消失了。几个警告:

1) 它不验证数据。 2) 它使用 SQL INSERT 命令的形式,读起来像 ...

INSERT INTO <table> (field-1, field-2, ...) 
       VALUES (value-1-1, value-1-2, ...), (value-2-1, value-2-2, ...), ...`

... 这可能不是所有数据库的正确语法,但它适用于 Postgres。为您的 SQL 版本更改适当语法的代码并不难。

在我的特定情况下,使用“model.create!”将 19K+ 记录插入我的开发机器(配备 8GB RAM、2.4GHz Intel Core i5 和 SSD 的 MacBook Pro)上的一个简单表中,从 223 秒开始使用 'model.import!' 到 7.2 秒。

class ActiveRecord::Base

  def self.import!(record_list)
    raise ArgumentError "record_list not an Array of Hashes" unless record_list.is_a?(Array) && record_list.all? |rec| rec.is_a? Hash 
    key_list, value_list = convert_record_list(record_list)        
    sql = "INSERT INTO #self.table_name (#key_list.join(", ")) VALUES #value_list.map |rec| "(#rec.join(", "))" .join(" ,")"
    self.connection.insert_sql(sql)
  end

  def self.convert_record_list(record_list)
    key_list = record_list.map(&:keys).flatten.uniq.sort

    value_list = record_list.map do |rec|
      list = []
      key_list.each |key| list <<  ActiveRecord::Base.connection.quote(rec[key]) 
      list
    end

    return [key_list, value_list]
  end
end

【讨论】:

【参考方案2】:

使用activerecord-import gem。假设您正在读取一个 CSV 文件并生成一个Product 目录,并且您希望以 1000 条为一组插入记录:

batch,batch_size = [], 1_000 
CSV.foreach("/data/new_products.csv", :headers => true) do |row|
  batch << Product.new(row)

  if batch.size >= batch_size
    Product.import batch
    batch = []
  end
end
Product.import batch

【讨论】:

我在回答中确实提到了 activerecord-import,但它没有解决我的具体情况,即哈希数组(我相信这是一个非常典型的用例;它当然适合我)。底线:如果 ar-import 支持哈希数组,我会使用它而不是编写自己的代码。我把它作为另一种选择。 我错过了你提到的activerecord-import。我给出的示例处理哈希数组(csv 行是一个哈希)。如果你已经有了哈希数组,你可以使用上面使用的技术来处理它们。 在活动记录文档中查看此内容,发现create method # Creating an Array of new objects using a block, where the block is executed for each object: User.create([ :first_name =&gt; 'Jamie' , :first_name =&gt; 'Jeremy' ]) do |u|     u.is_admin = false   end 中实际上内置了支持该功能的功能。 @matov 确实如此,但效率低下。它为每条记录生成一个 SQL 插入。尝试对 10000 条记录执行此操作 - 这相当慢。 @JackR-G 是的,我知道。有一些 gem 可以支持这种功能,比如你提到的 activerecord-import,但我发现在处理更大的数据集时还不够好。【参考方案3】:

我开始遇到大量记录 (> 10000) 的问题,因此我修改了代码以一次以 1000 条记录为一组工作。这是新代码的链接:

https://gist.github.com/jackrg/76ade1724bd816292e4e

【讨论】:

感谢您的要点。我正在使用AR-SQLServer-adapter,我不得不将self.connection.insert_sql(sql) 更改为self.connection.execute(sql)。真快! 很高兴您可以使用它。与创建相比,它非常快!(哈希数组) 是的,我发现它快了 70 倍!我已根据您的代码进行了 bulk_update:***.com/a/25430089/873650 不错!巧妙地使用 FROM 语法将目标与 CSV 列表连接起来。 关于步进,您应该使用:(0..record_list.count-1).step(1000).each do |start|key_list, value_list = convert_record_list(record_list[start..start+999]) 参见 gist cmets。【参考方案4】:

您也可以使用activerecord-insert_many gem。只需创建一个对象数组!

events = [name: "Movie Night", time: "10:00", name: "Tutoring", time: "7:00", ...]

Event.insert_many(events)

【讨论】:

真的很好,3 分钟内记录了 100 万条记录...谢谢。【参考方案5】:

使用事务可以大大加快批量插入的速度!

Model.transaction do
    many.times Model.create! 
end

如果涉及到多个模型,为每个模型做一个Model.transaction,这会受到影响:

Model1.transaction do
    Model2.transaction do
        many.times do
            m1 = Model1.create!
            m1.add_model2
        end
    end
end

【讨论】:

我在一个事务中创建了 1000 条记录,它仍然是迭代的,需要 30 秒。我认为这不是一个好的解决方案。 是的。但这只是解决方案的一部分。要进一步提高性能,您可以使用纯 SQL。如果您提交每条记录而不是使用事务,则插入会慢得多。【参考方案6】:

对于 Rails 6.x,请使用 insert_all。

【讨论】:

以上是关于将记录批量插入 Active Record 表的主要内容,如果未能解决你的问题,请参考以下文章

使用 LINQ to SQL 批量插入和复制记录

批量 MySQL 插入,一个主记录,一个带有外键的详细记录

千万条数据批量插入数据库表

在实体框架中批量插入后批量插入记录并获取它们的 ID

MySQL群集中的批量插入和更新

批量插入在 docker 容器中运行的 Postgres 数据库挂起