【问题标题】:Saving multiple objects in a single call in rails在 Rails 中的一次调用中保存多个对象
【发布时间】:2011-01-31 08:19:00
【问题描述】:

我在 rails 中有一个方法正在做这样的事情:

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

问题是我添加的实体越多,这需要的时间就越长。我怀疑这是因为它必须为每条记录访问数据库。由于它们是嵌套的,我知道在拯救父母之前我无法拯救孩子,但我想一次拯救所有父母,然后拯救所有孩子。这样做会很好:

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)

只需两次数据库命中即可完成所有操作。有没有一种简单的方法可以在 Rails 中做到这一点,还是我一次只能做一个?

【问题讨论】:

    标签: ruby-on-rails activerecord data-access


    【解决方案1】:

    您可以尝试使用 Foo.create 而不是 Foo.new。创建“如果验证通过,则创建一个对象(或多个对象)并将其保存到数据库。无论对象是否成功保存到数据库,都会返回结果对象。”

    您可以像这样创建多个对象:

    # Create an Array of new objects
      parents = Foo.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])
    

    然后,对于每个父级,您还可以使用 create 添加到其关联:

    parents.each do |parent|
      parent.children.create (:child_name => 'abc')
    end
    

    我建议阅读ActiveRecord documentationActiveRecord query interfaceActiveRecord associations 上的Rails 指南。后者包含声明关联时类获得的所有方法的指南。

    【讨论】:

    • 不幸的是,ActiveRecord 将为每个创建的模型生成一个 INSERT 查询。 OP 想要一个单独的 INSERT 调用,而 ActiveRecord 不会这样做。
    • 是的,我希望在一次插入调用中获得所有内容,但如果 activerecord 不是那么聪明,我想这不是很容易。
    • @FrançoisBeausoleil 你介意看一下问题stackoverflow.com/questions/15386450/…,这就是我不能同时插入多条记录的原因吗?
    • 确实不能让 AR 生成一个 INSERT 或 UPDATE,但使用 ActiveRecord::Base.transaction { records.each(&:save) } 或类似的,您至少可以将所有 INSERT 或 UPDATE 放入一个事务中。
    • 实际上,OP 希望减少对数据库的访问,以加快数据库访问速度,而 ActiveRecord 实际上让您通过在一个事务中批量处理所有调用来做到这一点。 (请参阅 Harish 的答案,这应该是公认的答案。)ActiveRecord 不允许您做的是让数据库为每个事务创建一个 INSERT 查询,但这并不重要,因为延迟来自网络访问数据库,而不是在执行 INSERT 查询时访问数据库本身。
    【解决方案2】:

    由于您需要执行多次插入,因此数据库将被多次命中。您的情况延迟是因为每次保存都是在不同的数据库事务中完成的。您可以通过将所有操作包含在一个事务中来减少延迟。

    class Foo
      belongs_to  :parent,   :class_name => "Foo"
      has_many    :children, :class_name => "Foo", :foreign_key=> "parent_id"
    end
    

    您的保存方法可能如下所示:

    # 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 调用保存子对象。

    【讨论】:

    • 正是我想要的。大大加快了我的种子速度。谢谢:-)
    【解决方案3】:

    在其他地方找到的两个答案之一:Beerlington。 这两个是你最好的表现


    我认为在性能方面最好的选择是使用 SQL,并在每个查询中批量插入多行。如果您可以构建一个执行以下操作的 INSERT 语句:

    插入 foos_bars (foo_id,bar_id) 值 (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。没错,没有模型就不行,但是你可以为导入创建一个模型。

    
    FooBar.import [:foo_id, :bar_id], [[1,2], [1,3]]
    

    干杯

    【讨论】:

    • 这很适合插入,但是在一个事务中更新多条记录呢?
    • 对于更新,您应该使用 upsert:github.com/seamusabshere/upsert。欢呼
    • 对 sql 查询的想法很糟糕。您应该使用 ActiveRecord 和事务。
    • 这不是一个坏主意。如果你正在做一个插入,它会成功或失败,我猜不需要事务。或者,您始终可以将该 ONE 插入包装在事务块中。
    • 这是糟糕的 Rails 做法
    【解决方案4】:

    您无需宝石即可快速击中 DB,而且只需一次!

    Jackrg 为我们解决了这个问题: https://gist.github.com/jackrg/76ade1724bd816292e4e

    【讨论】:

    • Mongodb 有这样的解决方案吗?
    【解决方案5】:

    你需要使用这个 gem "FastInserter" -> https://github.com/joinhandshake/fast_inserter

    并且插入大量和数千条记录很快,因为这个 gem 跳过活动记录,并且只使用单个 sql 原始查询

    【讨论】:

    • 虽然 gem 的链接可能有用,但请提供一些提问者可以使用的代码,而不是他们当前的代码(参见问题)。
    • 答案需要嵌入的基本信息。请编辑您的答案并在此处添加链接,并将其基本部分添加到答案中,使其独立。
    【解决方案6】:

    insert_all (Rails 6+)

    Rails 6 引入了一种新方法insert_all,它在单个SQL INSERT 语句中将多条记录插入到数据库中。

    此外,此方法不实例化任何模型并且不调用 Active Record 回调或验证

    所以,

    Foo.insert_all([
      { first_name: 'Jamie' },
      { first_name: 'Jeremy' }
    ])
    

    效率高很多
    Foo.create([
      { first_name: 'Jamie' },
      { first_name: 'Jeremy' }
    ])
    

    如果您只想插入新记录。

    【讨论】:

    • 我等不及要更新我们的应用程序了。 Rails 6 中有很多很酷的东西。
    • 需要注意的一点是:insert_all 跳过了 AR 回调和验证:edgeguides.rubyonrails.org/…
    • 如果要验证实体,请检查insert_all!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-01-10
    • 1970-01-01
    • 1970-01-01
    • 2010-12-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多