Lưu nhiều đối tượng trong một lần gọi trong đường ray


93

Tôi có một phương thức trong rails đang làm một cái gì đó như thế này:

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

Vấn đề là điều này mất nhiều thời gian hơn và lâu hơn khi tôi thêm nhiều thực thể. Tôi nghi ngờ điều này là do nó phải truy cập vào cơ sở dữ liệu cho mọi bản ghi. Vì chúng lồng vào nhau nên tôi biết mình không thể cứu lũ trẻ trước khi cứu được bố mẹ, nhưng tôi muốn cứu tất cả bố mẹ cùng một lúc, và sau đó là tất cả những đứa trẻ. Sẽ rất tuyệt nếu làm điều gì đó như:

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)

Điều đó sẽ làm tất cả chỉ trong hai lần truy cập cơ sở dữ liệu. Có cách nào dễ dàng để thực hiện việc này trong đường ray không, hay tôi đang gặp khó khăn khi làm từng cái một?

Câu trả lời:


69

Bạn có thể thử sử dụng Foo.create thay vì Foo.new. Tạo "Tạo một đối tượng (hoặc nhiều đối tượng) và lưu nó vào cơ sở dữ liệu, nếu các xác thực được thông qua. Đối tượng kết quả được trả về cho dù đối tượng đã được lưu thành công vào cơ sở dữ liệu hay chưa."

Bạn có thể tạo nhiều đối tượng như sau:

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

Sau đó, đối với mỗi phụ huynh, bạn cũng có thể sử dụng tạo để thêm vào liên kết của nó:

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

Tôi khuyên bạn nên đọc cả tài liệu ActiveRecord và Rails Guides trên giao diện truy vấn ActiveRecord và các liên kết ActiveRecord . Phần sau chứa một hướng dẫn về tất cả các phương thức mà một lớp nhận được khi bạn khai báo một liên kết.


78
Thật không may, ActiveRecord sẽ tạo một truy vấn INSERT cho mỗi mô hình đã tạo. OP muốn một lệnh gọi INSERT duy nhất, điều này ActiveRecord sẽ không thực hiện.
François Bea lăng xê

Vâng, tôi đã hy vọng có được tất cả trong một lần gọi chèn, nhưng nếu activerecord không thông minh như vậy, tôi đoán nó không dễ dàng lắm.
captncraig

@ FrançoisBeaosystemil, bạn có phiền khi xem câu hỏi stackoverflow.com/questions/15386450/… , đây có phải là lý do tại sao tôi không thể chèn nhiều bản ghi cùng một lúc không?
Richlewis

3
Đúng là bạn không thể sử dụng AR để tạo một INSERT hoặc UPDATE, nhưng với ActiveRecord::Base.transaction { records.each(&:save) }hoặc tương tự, ít nhất bạn có thể đặt tất cả INSERT hoặc UPDATE vào một giao dịch duy nhất.
yuval

1
Trên thực tế, OP muốn đánh vào cơ sở dữ liệu ít hơn, để tăng tốc độ truy cập DB và ActiveRecord thực sự cho phép bạn làm điều đó, bằng cách gộp tất cả các lệnh gọi trong một giao dịch. (Xem câu trả lời của Harish, phải là câu trả lời được chấp nhận.) Điều mà ActiveRecord sẽ không cho phép bạn làm là khiến DB tạo một truy vấn CHÈN cho mỗi giao dịch, nhưng điều đó không quan trọng lắm, vì độ trễ đến từ việc thực hiện mạng truy cập vào DB và không phải bên trong chính DB khi nó thực hiện các truy vấn INSERT.
Magne

99

Vì bạn cần thực hiện nhiều lần chèn, cơ sở dữ liệu sẽ bị tấn công nhiều lần. Sự chậm trễ trong trường hợp của bạn là do mỗi lần lưu được thực hiện trong các giao dịch DB khác nhau. Bạn có thể giảm độ trễ bằng cách gộp tất cả các thao tác của mình trong một giao dịch.

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

Phương thức lưu của bạn có thể trông giống như sau:

# 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

Lời savegọi trên đối tượng cha lưu các đối tượng con.


3
Chỉ cần những gì tôi đang tìm kiếm. Tăng tốc hạt giống của tôi rất nhiều. Cảm ơn :-)
Renra

16

insert_all (Rails 6+)

Rails 6đã giới thiệu một phương thức insert_all mới , chèn nhiều bản ghi vào cơ sở dữ liệu trong một SQL INSERTcâu lệnh.

Ngoài ra, phương pháp này không khởi tạo bất kỳ mô hình nào và không gọi các lệnh gọi lại hoặc xác nhận Active Record.

Vì thế,

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

nó hiệu quả hơn đáng kể so với

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

nếu tất cả những gì bạn muốn làm là chèn các bản ghi mới.


1
Tôi không thể đợi cho đến khi chúng tôi cập nhật ứng dụng của mình. Rất nhiều điều thú vị trong Rails 6.
Dan

1
một điều cần lưu ý là: insert_all bỏ qua lệnh gọi lại và xác thực AR: edgeguides.rubyonrails.org/…
sujay

11

Một trong hai câu trả lời được tìm thấy ở một nơi khác: bởi Beerlington . Hai cái đó là đặt cược tốt nhất của bạn cho hiệu suất


Tôi nghĩ đặt cược tốt nhất về hiệu suất của bạn là sử dụng SQL và chèn hàng loạt nhiều hàng cho mỗi truy vấn. Nếu bạn có thể xây dựng một câu lệnh INSERT có tác dụng như sau:

CHÈN VÀO foos_bars (foo_id, bar_id) VALUES (1,1), (1,2), (1,3) .... Bạn sẽ có thể chèn hàng nghìn hàng trong một truy vấn duy nhất. Tôi đã không thử phương pháp mass_habtm của bạn, nhưng có vẻ như bạn có thể làm như sau:


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}")

Ngoài ra, nếu bạn đang tìm kiếm Thanh theo "some_attribute", hãy đảm bảo rằng bạn đã lập chỉ mục trường đó trong cơ sở dữ liệu của mình.


HOẶC LÀ

Bạn vẫn có thể xem xét nhập bản ghi hoạt động. Đúng là nó không hoạt động nếu không có mô hình, nhưng bạn có thể tạo một Mô hình chỉ để nhập.


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

Chúc mừng


Điều đó hoạt động tốt để chèn, nhưng còn để cập nhật nhiều bản ghi trong một giao dịch thì sao?
Avishai 23/09/13

2
Để cập nhật, bạn nên sử dụng upsert: github.com/seamusabshere/upsert . hoan hô
Nguyễn Chiến Công

Ý tưởng rất kém với truy vấn sql. Bạn nên sử dụng ActiveRecord và giao dịch.
Kerozu

Đó không phải là một ý kiến ​​tồi. Nếu bạn đang thực hiện MỘT lần chèn, nó sẽ thành công hoặc thất bại, tôi đoán vậy, không cần giao dịch. Hoặc bạn luôn có thể bọc MỘT chèn đó trong một khối giao dịch.
Fernando Fabreti

đây là thực hành đường ray không tốt
Blair Anderson

0

bạn cần sử dụng đá quý này "FastInserter" -> https://github.com/joinhandshake/fast_inserter

và việc chèn một số lượng lớn và hàng nghìn bản ghi rất nhanh vì đá quý này bỏ qua bản ghi đang hoạt động và chỉ sử dụng một truy vấn thô sql duy nhất


1
Mặc dù liên kết đến viên ngọc có thể hữu ích, vui lòng cung cấp một số mã mà Người hỏi có thể sử dụng thay vì mã hiện tại của họ (xem câu hỏi).
trincot


1
Các câu trả lời cần có thông tin thiết yếu được nhúng . Vui lòng chỉnh sửa câu trả lời của bạn và thêm liên kết vào đó, đồng thời thêm các phần thiết yếu của nó vào bên trong câu trả lời, để câu trả lời được khép kín.
trincot

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.