web-dev-qa-db-fra.com

Bulk Insert records dans la table Active Record

J'ai constaté que l'exécution de mes instructions Model.create! prenait beaucoup de temps lorsque j'ajoutais un grand nombre d'enregistrements à la fois. Regardé ActiveRecord-Import mais cela ne fonctionnait pas avec un tableau de hachages (ce qui est ce que j'ai et qui est assez commun, je pense). Comment puis-je améliorer la performance?

15
Jack R-G

J'ai commencé à rencontrer des problèmes avec un grand nombre d'enregistrements (> 10000), j'ai donc modifié le code pour qu'il fonctionne par groupes de 1 000 enregistrements à la fois. Voici un lien vers le nouveau code: 

https://Gist.github.com/jackrg/76ade1724atz16292e4e

5
Jack R-G

Utilisez le activerecord-import gem. Disons que vous lisez un fichier CSV et générez un catalogue Product et que vous souhaitez insérer des enregistrements par lots de 1 000:

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
22
Harish Shetty

Merci à Chris Heald @cheald pour son article 2009 article , qui m’a montré que la commande d’insertion sur plusieurs lignes était la meilleure solution.

Ajout du code suivant à mon fichier initializers/active_record.rb, modification de mes appels Model.create!(...) en Model.import!(...) et tout va bien. Quelques mises en garde:

1) Il ne valide pas les données.
2) Il utilise la forme de la commande SQL INSERT qui se lit comme ...

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

... qui peut ne pas être la syntaxe correcte pour toutes les bases de données, mais cela fonctionne avec Postgres. Il ne serait pas difficile de modifier le code pour la syntaxe appropriée pour votre version SQL.

Dans mon cas particulier, l'insertion d'enregistrements 19K + dans un tableau simple sur ma machine de développement (MacBook Pro avec 8 Go de RAM, processeur Intel Core i5 à 2,4 GHz et SSD) passait de 223 secondes à l'aide de 'model.create!'. à 7,2 secondes en utilisant un '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
10
Jack R-G

Vous pouvez également utiliser le gem activerecord-insert_many . Il suffit de faire un tableau d'objets!

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

Event.insert_many(events)
1
Luke

L'utilisation d'une transaction accélère énormément les encarts en vrac!

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

Si plusieurs modèles sont impliqués, effectuez une Model.transaction pour chaque modèle, qui est affecté:

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