在Rails中创建大规模HABTM关联的最快方法是什么?

时间:2010-02-04 21:37:58

标签: ruby-on-rails activerecord has-and-belongs-to-many

我有两个表,在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.bars = @bars
@foo.bars << @bars

两者都运行得非常慢,每个bar都有如下条目:

  

bars_foos列(1.1ms)显示   FIELDS FROM bars_foos SQL(0.6ms)   INSERT INTO bars_foosbar_id,   foo_id)VALUES(100,117200)

我查看了ar-extensions,但import函数似乎没有模型(Model.import),它排除了它用于连接表。

我是否需要编写SQL,或者Rails是否有更漂亮的方式?

4 个答案:

答案 0 :(得分:7)

我认为在性能方面最好的选择是使用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,请确保在数据库中将该字段编入索引。

答案 1 :(得分:6)

您仍然可以查看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

答案 2 :(得分:1)

这比等效的本机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上发现相同。

答案 3 :(得分:-4)

老实说,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

此外,你应该尝试在生产中运行相同的内容,看看你得到了什么样的性能,因为很多缓存都在生产中继续进行,但不一定会在开发中发生。