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

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

【问题讨论】:

标签: ruby-on-rails activerecord bulkinsert


【解决方案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:stackoverflow.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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-12-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多