问题
I have a method in rails that is doing something like this:
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
The problem is this takes longer and longer the more entities I add. I suspect this is because it has to hit the database for every record. Since they are nested, I know I can't save the children before the parents are saved, but I would like to save all of the parents at once, and then all of the children. It would be nice to do something like:
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)
That would do it all in only two database hits. Is there an easy way to do this in rails, or am I stuck doing it one at a time?
回答1:
You might try using Foo.create instead of Foo.new. Create "Creates an object (or multiple objects) and saves it to the database, if validations pass. The resulting object is returned whether the object was saved successfully to the database or not."
You can create multiple objects like this:
# Create an Array of new objects
parents = Foo.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])
Then, for each parent, you can also use create to add to its association:
parents.each do |parent|
parent.children.create (:child_name => 'abc')
end
I recommend reading both the ActiveRecord documentation and the Rails Guides on ActiveRecord query interface and ActiveRecord associations. The latter contains a guide of all the methods a class gains when you declare an association.
回答2:
Since you need to perform multiple inserts, database will be hit multiple times. The delay in your case is because each save is done in different DB transactions. You can reduce the latency by enclosing all your operations in one transaction.
class Foo
belongs_to :parent, :class_name => "Foo"
has_many :children, :class_name => "Foo", :foreign_key=> "parent_id"
end
Your save method might look like this:
# 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
The save
call on the parent object saves the child objects.
回答3:
One of the two answers found somewhere else: by Beerlington. Those two are your best bet for performance
I think your best bet performance-wise is going to be to use SQL, and bulk insert multiple rows per query. If you can build an INSERT statement that does something like:
INSERT INTO foos_bars (foo_id,bar_id) VALUES (1,1),(1,2),(1,3).... You should be able to insert thousands of rows in a single query. I didn't try your mass_habtm method, but it seems like you could to something like:
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}")
Also, if you are searching Bar by "some_attribute", make sure you have that field indexed in your database.
OR
You still might have a look at activerecord-import. It's right that it doesn't work without a model, but you could create a Model just for the import.
FooBar.import [:foo_id, :bar_id], [[1,2], [1,3]]
Cheers
回答4:
You don't need a gem to hit DB fast and only once!
Jackrg has worked it out for us: https://gist.github.com/jackrg/76ade1724bd816292e4e
回答5:
you need to use this gem "FastInserter" -> https://github.com/joinhandshake/fast_inserter
and inserting a large number and thousands of records is fast because this gem skips active record, and only uses a single sql raw query
来源:https://stackoverflow.com/questions/2509320/saving-multiple-objects-in-a-single-call-in-rails