Сохранение нескольких объектов за один вызов в 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)

Это сделало бы все это всего за два обращения к базе данных. Есть ли простой способ сделать это в рельсах, или я застрял, делая это по одному?


person captncraig    schedule 24.03.2010    source источник


Ответы (6)


Вы можете попробовать использовать 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, так и руководства по Rails на интерфейс запросов ActiveRecord и ассоциации ActiveRecord. Последний содержит руководство по всем методам, которые получает класс при объявлении ассоциации.

person Roadmaster    schedule 24.03.2010
comment
К сожалению, ActiveRecord будет генерировать один запрос INSERT для каждой созданной модели. OP хочет один вызов INSERT, чего ActiveRecord не сделает. - person François Beausoleil; 24.03.2010
comment
Да, я надеялся получить все это за один вызов вставки, но если activerecord не так умен, думаю, это не очень просто. - person captncraig; 13.04.2010
comment
@FrançoisBeausoleil, не могли бы вы взглянуть на вопрос stackoverflow.com/questions/15386450/, поэтому я не могу вставлять несколько записей одновременно? - person Richlewis; 13.03.2013
comment
Это правда, что вы не можете заставить AR сгенерировать один INSERT или UPDATE, но с ActiveRecord::Base.transaction { records.each(&:save) } или подобным вы можете, по крайней мере, поместить все INSERT или UPDATE в одну транзакцию. - person yuval; 01.09.2016
comment
Я нахожусь за пределами своего окна редактирования, но повторю: мой собственный комментарий выше: Остерегайтесь блокировки таблицы во время большой транзакции. - person yuval; 01.09.2016
comment
На самом деле OP хочет меньше обращаться к базе данных, чтобы ускорить доступ к БД, и ActiveRecord фактически позволяет вам это сделать, объединяя все вызовы в одну транзакцию. (См. ответ Хариша, который должен быть принятым ответом.) Что ActiveRecord не позволит вам сделать, так это заставить БД создавать один запрос INSERT на транзакцию, но это не имеет большого значения, поскольку задержка возникает из-за выполнения сети доступ к БД, а не внутри самой БД, когда она выполняет запросы INSERT. - person Magne; 16.12.2016
comment
Или вы можете попробовать github.com/zdennis/activerecord-import, как предложил Нгуен Чен Конг, что фактически также создаст один единственный запрос INSERT для БД. - person Magne; 16.12.2016

Поскольку вам нужно выполнить несколько вставок, база данных будет поражена несколько раз. Задержка в вашем случае связана с тем, что каждое сохранение выполняется в разных транзакциях БД. Вы можете уменьшить задержку, заключив все свои операции в одну транзакцию.

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 для родительского объекта сохраняет дочерние объекты.

person Harish Shetty    schedule 24.03.2010
comment
Как раз то, что я искал. Ускоряет мои семена много. Спасибо :-) - person Renra; 01.07.2014

insert_all (Rails 6+)

Rails 6 представил новый метод insert_all, который вставляет несколько записей в базу данных в одном операторе SQL INSERT.

Кроме того, этот метод не создает экземпляры моделей и не вызывает обратные вызовы или проверки Active Record.

So,

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

он значительно эффективнее, чем

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

если все, что вы хотите сделать, это вставить новые записи.

person Marian13    schedule 30.01.2020
comment
Я не могу дождаться, когда мы обновим наше приложение. Так много классных вещей в Rails 6. - person Dan; 02.06.2020
comment
Следует отметить одну вещь: insert_all пропускает обратные вызовы и проверки AR: - person sujay; 02.09.2020

Один из двух ответов, найденных где-то еще: 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}")

Кроме того, если вы ищете Bar по «some_attribute», убедитесь, что это поле проиндексировано в вашей базе данных.


OR

Вы все еще можете взглянуть на activerecord-import. Это правильно, что без модели это не работает, но вы можете создать модель только для импорта.


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

Ваше здоровье

person Nguyen Chien Cong    schedule 12.03.2012
comment
Это прекрасно работает для вставки, но как насчет обновления нескольких записей в одной транзакции? - person Avishai; 23.09.2013
comment
Для обновления используйте upsert: github.com/seamusabshere/upsert. ваше здоровье - person Nguyen Chien Cong; 27.09.2013
comment
Очень плохая идея с запросом sql. Вы должны использовать ActiveRecord и транзакцию. - person Kerozu; 16.06.2014
comment
Это неплохая идея. Если вы делаете ОДНУ вставку, она будет либо успешной, либо неудачной, я думаю, нет необходимости в транзакции. Или вы всегда можете обернуть эту ОДНУ вставку в блок транзакции. - person Fernando Fabreti; 21.08.2014
comment
это плохая практика рельсов - person Blair Anderson; 18.11.2016

вам нужно использовать этот гем «FastInserter» -> https://github.com/joinhandshake/fast_inserter

и вставка большого количества и тысяч записей выполняется быстро, потому что этот драгоценный камень пропускает активную запись и использует только один необработанный запрос sql

person val caro    schedule 25.01.2019
comment
Хотя ссылка на драгоценный камень может быть полезной, предоставьте некоторый код, который Спрашивающий мог бы использовать вместо своего текущего кода (см. вопрос). - person trincot; 25.01.2019
comment
здесь: stackoverflow.com/questions/19081129/ - person val caro; 27.01.2019
comment
В ответы должна быть встроена основная информация. Пожалуйста, отредактируйте свой ответ и добавьте туда ссылку, а также добавьте его основные части в ответ, чтобы он был автономным. - person trincot; 27.01.2019

Вам не нужен драгоценный камень, чтобы быстро попасть в БД и только один раз!

Jackrg сделал это за нас: https://gist.github.com/jackrg/76ade1724bd816292e4e

person Fernando Fabreti    schedule 21.08.2014
comment
Любое решение, подобное этому для Mongodb? - person Breno; 19.03.2016