2013-03-10 5 views
13

Ho scoperto che le mie istruzioni Model.create! richiedevano molto tempo per essere eseguite quando ho aggiunto un numero elevato di record contemporaneamente. Guardato a ActiveRecord-Import ma non ha funzionato con una serie di hash (che è quello che ho e che penso sia piuttosto comune). Come posso migliorare le prestazioni?Inserimento di record in blocco nella tabella dei record attivi

+0

Vedi anche: [Come implementare inserimento di massa in Rails 3] (http: // stackoverflow.com/questions/8505263/how-to-implement-bulk-insert-in-rails-3) e [Inserimento batch in rails 3] (http://stackoverflow.com/questions/15784305/batch-insertion-in -rails-3). –

+0

@Cupcake Questi due riferimenti parlano dell'uso di ActiveRecord-Import (che non supporta un array di hash come create! Fa) e della scrittura diretta di SQL. Vedi la risposta accettata per quello che ho fatto. –

+0

questa gemma https://github.com/bjhaid/active_record_bulk_insert mira a risolvere questo tipo di sfida – bjhaid

risposta

4

ho iniziato a correre in problemi con un gran numero di record (> 10000), così ho modificato il codice per lavorare in gruppi di 1000 record alla volta. Questo è il link per il nuovo codice:

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

+0

Grazie per il tuo succo. Sto usando 'AR-SQLServer-adapter' e ho dovuto cambiare' self.connection.insert_sql (sql) 'TO' self.connection.execute (sql) '. Davvero veloce! –

+0

Felice che tu possa farne uso. È molto veloce rispetto a creare! (Array-of-hashes) –

+0

sì, l'ho trovato 70 volte più veloce! Ho creato un bulk_update in base al tuo codice: http://stackoverflow.com/a/25430089/873650 –

10

Grazie a Chris Heald @cheald per il suo article 2009, con mi ha mostrato che il modo migliore per andare era il comando di inserimento multi-riga.

Aggiunto il seguente codice al mio file initializers/active_record.rb, cambiato le mie chiamate Model.create!(...) a Model.import!(...) e via va. Un paio di avvertimenti:

1) Non convalida i dati.
2) Si utilizza la forma del comando SQL INSERT che si legge come ...

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

... che non può essere la sintassi corretta per tutti i database, ma funziona con Postgres. Non sarebbe difficile modificare il codice per la sintassi appropriata per la versione SQL.

Nel mio caso particolare, l'inserimento di record 19K + in una semplice tabella sulla mia macchina di sviluppo (MacBook Pro con 8 GB di RAM, Intel Core i5 e Intel SSD da 2,4 GHz andava da 223 secondi utilizzando 'model.create!' a 7,2 secondi utilizzando 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 
19

Utilizzare la gemma activerecord-import. Diciamo che si sta leggendo un file CSV e la generazione di un catalogo Product e si desidera inserire record in lotti di 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

Nella mia risposta ho menzionato activerecord-import, ma non affronta il mio caso specifico, che è una serie di hash (credo che questo sia un tipico caso d'uso, certamente per me). In conclusione: se ar-import avesse il supporto per un array di hash, l'avrei usato e non avrebbe scritto il mio codice. L'ho messo là fuori come altra alternativa. –

+1

Mi mancava la tua menzione di "activerecord-import". L'esempio che ho dato si occupa di array di hash (la riga csv è un hash). Se disponi già della matrice di hash, puoi elaborarli utilizzando la tecnica sopra utilizzata. –

+0

Esaminando questo nei documenti di registrazione attivi e scoprendo che in realtà esiste funzionalità incorporate per supportarlo nel [metodo di creazione] (http://railsapi.com/doc/rails-v2.3.8/classes/ActiveRecord/Base.html # M000969) '# Creazione di una matrice di nuovi oggetti utilizzando un blocco, in cui viene eseguito il blocco per ciascun oggetto: User.create ([{: first_name => 'Jamie'}, {: first_name => 'Jeremy' }]) do | u | u.is_admin = falso fine' – matov

1

È inoltre possibile utilizzare il activerecord-insert_many gemma. Crea una serie di oggetti!

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

Event.insert_many(events) 
+0

davvero bello, 1 milione di record in 3 minuti ... grazie. –

0

L'utilizzo di una transazione velocizza notevolmente gli inserimenti di grandi quantità!

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

Se più modelli sono coinvolti, fare un Model.transaction per ogni modello, che è influenzata:

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

Creo 1000 record in una transazione, è ancora iterativo e richiede 30 secondi. Non penso che questa sia una buona soluzione. –

+0

Lo è. Ma è solo una parte della soluzione. Per migliorare ulteriormente le prestazioni, è possibile utilizzare SQL semplice. Se si commettono i record anziché utilizzare le transazioni, gli inserimenti sono molto più lenti. – tvw