批量将记录插入Active Record表

Jac*_*R-G 15 activerecord bulkinsert ruby-on-rails

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

Har*_*tty 23

使用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
Run Code Online (Sandbox Code Playgroud)

  • 我在答案中确实提到了activerecord-import,但它没有解决我的具体案例,这是一个哈希数组(我相信这是一个非常典型的用例;它当然适合我).底线:如果ar-import支持哈希数组,我会使用它而不是编写我自己的代码.我把它作为另一种选择. (3认同)
  • 我错过了你提到的“activerecord-import”。我给出的示例涉及哈希数组(csv 行是哈希)。如果您已经拥有哈希数组,则可以使用上面使用的技术来处理它们。 (2认同)
  • 在活动记录文档中查看这一点,发现实际上有内置功能来支持[create方法](http://railsapi.com/doc/rails-v2.3.8/classes/ActiveRecord/Base.html# M000969) `# 使用块创建新对象数组,其中为每个对象执行块: User.create([{ :first_name =&gt; 'Jamie' }, { :first_name =&gt; 'Jeremy' }]) do |你| u.is_admin = 假结束` (2认同)
  • @matov 这是真的,但效率低下。它为每条记录生成一个 SQL 插入。尝试对 10000 条记录执行此操作 - 速度相当慢。 (2认同)

Jac*_*R-G 10

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

将以下代码添加到我的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, ...), ...`
Run Code Online (Sandbox Code Playgroud)

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

在我的特殊情况下,将19K +记录插入我的开发机器上的简单表格(MacBook Pro,配备8GB RAM,2.4GHz Intel Core i5和SSD),使用'model.create!'从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
Run Code Online (Sandbox Code Playgroud)


Jac*_*R-G 5

我开始遇到大量记录(> 10000)的问题,所以我修改了代码,一次以1000个记录为一组.这是新代码的链接:

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


mat*_*tes 5

对于 Rails 6.x,请使用insert_all