Подтвердить что ты не робот

Массовая вставка записей в таблицу Active Record

Я обнаружил, что мои операторы Model.create! занимали очень много времени, когда я добавлял большое количество записей одновременно. Посмотрел ActiveRecord-Import, но он не работал с массивом хешей (это то, что у меня есть и которое, как мне кажется, довольно распространено). Как повысить производительность?

4b9b3361

Ответ 1

Я начал сталкиваться с проблемами с большим количеством записей (> 10000), поэтому я изменил код для работы в группах по 1000 записей одновременно. Вот ссылка на новый код:

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

Ответ 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

Ответ 3

Благодаря 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, ...), ...`

... который не может быть правильным синтаксисом для всех баз данных, но он работает с Postgres. Нетрудно изменить код для соответствующего синтаксиса для вашей версии SQL.

В моем конкретном случае вставка записей 19K + в простую таблицу на моей машине разработки (MacBook Pro с 8 ГБ оперативной памяти, 2,4 ГГц Intel Core i5 и SSD) пошла с 223 секунд, используя 'model.create!' до 7.2 секунд, используя "model.import!".

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

Ответ 4

Вы также можете использовать камень activerecord-insert_many. Просто сделайте массив объектов!

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

Event.insert_many(events)

Ответ 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