102

我在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中做到这一点,还是我一次只能做一个?

4

6 回答 6

105

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

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

于 2010-03-24T18:05:52.233 回答
73

您可以尝试使用 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 文档和关于ActiveRecord 查询接口ActiveRecord 关联的 Rails 指南。后者包含声明关联时类获得的所有方法的指南。

于 2010-03-24T16:18:17.773 回答
27

insert_all (Rails 6+)

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

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

所以,

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

它比

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

如果您只想插入新记录。

于 2020-01-30T22:13:12.897 回答
11

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


我认为在性能方面你最好的选择是使用 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。没有模型它就无法工作是对的,但是您可以创建一个模型仅用于导入。


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

干杯

于 2012-03-12T05:07:50.397 回答
0

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

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

于 2019-01-25T18:44:11.287 回答
-3

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

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

于 2014-08-21T11:13:32.967 回答