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?
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:
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
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
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)
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