在Rails中创建大规模HABTM关联的最快方法是什么?
我有两个表,在Rails中有HABTM关系。 类似于以下内容:
class Foo < ActiveRecord::Base has_and_belongs_to_many :bars end class Bar < ActiveRecord::Base has_and_belongs_to_many :foos end
现在我有了一个新的Foo
对象,并希望为它预先加载数千个条形码,我已预先加载:
@foo = Foo.create @bars = Bar.find_all_by_some_attribute(:a)
@foo = Foo.create @bars = Bar.find_all_by_some_attribute(:a)
最快的方法是什么? 我试过了:
@foo.bars = @bars @foo.bars << @bars
两者都运行得很慢,每个bar
都有如下条目:
bars_foos列(1.1ms)显示来自
bars_foos
SQL(0.6ms)INSERT INTObars_foos
(bar_id
,foo_id
)VALUES(foo_id
)
我查看了ar-extensions,但是如果没有一个模型(Model.import)排除了它用于连接表, import
函数似乎不起作用。
我需要编写SQL,还是Rails有更漂亮的方式?
我认为性能方面最好的选择是使用SQL,并为每个查询批量插入多行。 如果您可以构建一个类似于以下内容的INSERT语句:
INSERT INTO foos_bars (foo_id,bar_id) VALUES (1,1),(1,2),(1,3)....
您应该能够在单个查询中插入数千行。 我没有尝试你的mass_habtm方法,但似乎你可以这样:
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}")
此外,如果您通过“some_attribute”搜索Bar,请确保在数据库中将该字段编入索引。
您仍然可以查看activerecord-import 。 没有模型它是不行的,但你可以创建一个仅用于导入的模型。
class FooBar < ActiveRecord::Base; end FooBar.import [:foo_id, :bar_id], [[1,2], [1,3]]
您可以将其包装在事务中以确保HABTM完全填充,如下所示:
ActiveRecord::Base.transaction do imported_foo = Foo.import( foo_names, foo_values ) imported_bar = Bar.import( bar_names, bar_values ) FooBar.import( [:foo_id, :bar_id], imported_foo.ids.zip(imported_bar.ids) end
这比等效的本机rails代码要快7倍:
class << Foo def mass_habtm(attr_array) attr_str = attr_array.map{|a| %Q{'#{a}'} }.uniq.join(",") self.connection.execute(%Q{insert into foos_bars (foo_id,bar_id) select distinct foos.id,bars.id from foos,bars where foos.id = #{self.id} and bars.some_attribute in (#{attr_str})}) end end
在我看来,这是一个直截了当的操作,它应该在Rails中得到有效支持,我很想听听是否有人有更清洁的方式。
我正在运行2.2.2, 也许它在3.x中实现更高效? 并在3.0.2上发现了相同的内容。
老实说, has_and_belongs_to_many
是一种非常过时的做事方式。 您应该查看has_many :through
,这是进行连接表的新方法,并且已经有一段时间了。
class Foo < ActiveRecord::Base has_many :foobars has_many :bars, :through => :foobars def add_many_bars(bars) bars.each do |bar| self.bars << bar end end end class Bar < ActiveRecord::Base has_many :foobars has_many :foos, :through => :foobars end class FooBar < ActiveRecord::Base belongs_to :foo belongs_to :bar end
此外,您应该尝试在生产中运行相同的内容,并查看您获得的性能,因为生产中的许多缓存仍然在开发中不一定发生。