【问题标题】:What is the fastest way to create mass HABTM associations in Rails?在 Rails 中创建大量 HABTM 关联的最快方法是什么?
【发布时间】:2010-02-04 21:37:57
【问题描述】:

我有两个表,在 Rails 中有一个 HABTM 关系。类似于以下内容:

class Foo &lt ActiveRecord::Base has_and_belongs_to_many :bars end class Bar &lt 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 &lt&lt @bars

而且两者都运行得很慢,每个bar 的条目如下:

bars_foos 列 (1.1ms) 显示 来自bars_foos SQL 的字段(0.6 毫秒) 插入bars_foos (bar_id, foo_id) 值 (100, 117200)

我查看了 ar-extensions,但如果没有模型 (Model.import),import 函数似乎无法工作,因为模型 (Model.import) 无法将其用于连接表。

我需要写 SQL,还是 Rails 有更漂亮的方式?

【问题讨论】:

  • 真的吗?没有人?你们跳过上篮和“最佳实践”问题:)

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


【解决方案1】:

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

【讨论】:

  • 我的 mass_habtm 只是将查询组合成单个搜索/插入查询,这可能不会比您在此处获得的收益那么多。我讨厌自己选择,所以谢谢你至少给了我一个可行的选择。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    这比等效的原生 Rails 代码快 7 倍:

    class &lt&lt 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 上发现了同样的情况。

    【讨论】:

      【解决方案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
      

      此外,您应该尝试在生产环境中运行相同的程序,看看您会获得什么样的性能,因为在生产环境中会进行很多缓存,而在开发环境中不一定会发生。

      【讨论】:

      • 不要成为一个混蛋,但你没有解决主要问题 - 创建关系的速度,除了推测生产可能会更好。虽然缓存可能会有所帮助,但它几乎肯定不会改变 SQL 的制定方式。我还认为 habtm 是优化这些东西的更好候选者,因为 has_many->through 需要一个模型类,这意味着连接模型中可能存在回调。
      • 而且我很确定您的实现比我提出的不够快的实现
      • 是的,但是在连接上有一个模型可以让您使用查找器和其他您可能无法访问的选项做其他事情。至少这样,如果你想来回走动,你就有了另一个模型来放置你的代码,而不是在两个模型中重复它。
      猜你喜欢
      • 1970-01-01
      • 2019-01-07
      • 2014-06-07
      • 2010-09-18
      • 2010-11-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多