ruby-on-rails 将记录大容量插入活动记录表

tpxzln5u  于 2022-11-19  发布在  Ruby
关注(0)|答案(7)|浏览(250)

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

70gysomp

70gysomp1#

使用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
7rtdyuoh

7rtdyuoh2#

感谢Chris Heald @cheald编写的2009 article,with向我展示了最好的方法是多行插入命令。
在我的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版本修改适当语法的代码并不困难。
在我的特殊情况下,在我的开发机器(配备8 GB RAM、2.4GHz英特尔酷睿i5和SSD的MacBook Pro)上向一个简单的表中插入19 K+条记录的时间从使用'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
dgsult0t

dgsult0t3#

我开始遇到大量记录(〉10000)的问题,所以我修改了代码,一次可以处理1000条记录。下面是新代码的链接:
https://gist.github.com/jackrg/76ade1724bd816292e4e

sc4hvdpw

sc4hvdpw4#

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

x33g5p2x

x33g5p2x5#

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

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

Event.insert_many(events)
bvjveswy

bvjveswy6#

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

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

如果涉及多个模型,请为每个模型执行一个Model.事务,该事务受以下因素影响:

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

2ekbmq327#

https://stackoverflow.com/a/15318202/9732392这个答案解释得很好,但在我看来,如果我们使用数组而不是下面的Product.new(row),它可能会更快

batch,batch_size = [], 1000 
CSV.foreach("/data/new_products.csv", :headers => true) do |row|
  batch << [row[:part_number], row[:item_name], row[:cost]]

  if batch.size >= batch_size
    product_columns = [:part_number, :item_name, :cost]
    Product.import product_columns, batch, on_duplicate_key_update: {conflict_target: [:id], columns: [:part_number, :item_name, :cost]}
    batch = []
  end
end
Product.import product_columns, batch, on_duplicate_key_update: {conflict_target: [:id], columns: [:part_number, :item_name, :cost]} if batch.present?

更多信息请阅读?https://github.com/zdennis/activerecord-import

相关问题