Mam metodę w railsach, która robi coś takiego:
a = Foo.new("bar")
a.save
b = Foo.new("baz")
b.save
...
x = Foo.new("123", :parent_id => a.id)
x.save
...
z = Foo.new("zxy", :parent_id => b.id)
z.save
Problem polega na tym, że im więcej jednostek dodam, trwa to dłużej i dłużej. Podejrzewam, że dzieje się tak, ponieważ musi trafiać do bazy danych dla każdego rekordu. Ponieważ są zagnieżdżone, wiem, że nie mogę uratować dzieci, zanim rodzice zostaną uratowani, ale chciałbym uratować wszystkich rodziców naraz, a potem wszystkie dzieci. Byłoby miło zrobić coś takiego:
a = Foo.new("bar")
b = Foo.new("baz")
...
saveall(a,b,...)
x = Foo.new("123", :parent_id => a.id)
...
z = Foo.new("zxy", :parent_id => b.id)
saveall(x,...,z)
To wystarczyłoby tylko na dwa trafienia w bazie danych. Czy jest łatwy sposób na zrobienie tego w szynach, czy też utknąłem, robiąc to pojedynczo?
źródło
ActiveRecord::Base.transaction { records.each(&:save) }
lub czegoś podobnego możesz przynajmniej umieścić wszystkie INSERT lub UPDATE w jednej transakcji.Ponieważ musisz wykonać wiele wstawień, baza danych zostanie trafiona wiele razy. Opóźnienie w twoim przypadku jest spowodowane tym, że każdy zapis jest wykonywany w różnych transakcjach DB. Opóźnienie można zmniejszyć, zamykając wszystkie operacje w jednej transakcji.
class Foo belongs_to :parent, :class_name => "Foo" has_many :children, :class_name => "Foo", :foreign_key=> "parent_id" end
Twoja metoda zapisu może wyglądać następująco:
# build the parent and the children a = Foo.new(:name => "bar") a.children.build(:name => "123") b = Foo.new("baz") b.children.build(:name => "zxy") #save parents and their children in one transaction Foo.transaction do a.save! b.save! end
save
Wezwanie do obiektu nadrzędnego zapisuje obiektów podrzędnych.źródło
insert_all (szyny 6+)
Rails 6
wprowadził nową metodę insert_all , która wstawia wiele rekordów do bazy danych w jednejSQL INSERT
instrukcji.Ponadto ta metoda nie tworzy instancji żadnych modeli i nie wywołuje wywołań zwrotnych ani walidacji modułu Active Record.
Więc,
Foo.insert_all([ { first_name: 'Jamie' }, { first_name: 'Jeremy' } ])
jest znacznie wydajniejszy niż
Foo.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])
jeśli wszystko, co chcesz zrobić, to wstawić nowe rekordy.
źródło
Jedna z dwóch odpowiedzi znalezionych gdzie indziej: przez Beerlington . Te dwa to najlepszy sposób na wydajność
Myślę, że najlepszym rozwiązaniem pod względem wydajności będzie użycie SQL i zbiorcze wstawianie wielu wierszy na zapytanie. Jeśli możesz zbudować instrukcję INSERT, która robi coś takiego:
INSERT INTO foos_bars (foo_id, bar_id) VALUES (1,1), (1,2), (1,3) .... Powinieneś być w stanie wstawić tysiące wierszy w jednym zapytaniu. Nie próbowałem twojej metody Mass_habtm, ale wygląda na to, że możesz coś takiego:
bars = Bar.find_all_by_some_attribute(:a) foo = Foo.create values = bars.map {|bar| "(#{foo.id},#{bar.id})"}.join(",") connection.execute("INSERT INTO foos_bars (foo_id, bar_id) VALUES #{values}")
Ponadto, jeśli wyszukujesz Bar według "jakiegoś_atrybutu", upewnij się, że masz to pole zaindeksowane w swojej bazie danych.
LUB
Nadal możesz rzucić okiem na aktywny import nagrań. To prawda, że nie działa bez modelu, ale możesz utworzyć Model tylko do importu.
FooBar.import [:foo_id, :bar_id], [[1,2], [1,3]]
Twoje zdrowie
źródło
musisz użyć tego klejnotu „FastInserter” -> https://github.com/joinhandshake/fast_inserter
a wstawianie dużej liczby i tysięcy rekordów jest szybkie, ponieważ ten klejnot pomija aktywny rekord i używa tylko jednego surowego zapytania sql
źródło
Nie potrzebujesz klejnotu, aby szybko i tylko raz trafić w DB!
Jackrg to dla nas wypracował: https://gist.github.com/jackrg/76ade1724bd816292e4e
źródło