В соответствии с документами ActiveRecord:: Transactions:: ClassMethods, не новая вложенная транзакция будет игнорировать Откат. Из документов:
User.transaction do
User.create(username: 'Kotori')
User.transaction do
User.create(username: 'Nemu')
raise ActiveRecord::Rollback
end
end
raise ActiveRecord::Rollback
игнорируется, потому что он находится в дочерней транзакции (вернее, он все еще находится в рамках родительской транзакции, а не ее собственной). Я не понимаю, почему вызов Rollback будет проигнорирован обоими? Я вижу, что, поскольку "транзакция" для ребенка не является транзакцией, она не откатывает блок "Nemu", но почему он не вызывает откат для родителя? Скрывает ли дочерняя транзакция откат?
Другими словами, почему не существует способа отката родительской транзакции из внутри вложенного дочернего элемента?
На самом деле это именно то, как было разработано Вложенные транзакции. Я цитирую документы oracle:
Вложенная транзакция используется для предоставления транзакционной гарантии для подмножества операций, выполняемых в рамках более крупной транзакции. Это позволяет вам совершить и прервать подмножество операций независимо от более крупной транзакции.
Таким образом, дочерняя транзакция в обычной вложенной транзакции не имеет права говорить о том, как он или другие дети или родитель (большая транзакция) могут вести себя, кроме изменения взаимных данных или сбоев для исключения.
Но вы можете предоставить ему (детскую транзакцию) очень ограниченный шанс на участие в его судьбе, используя функцию sub-transaction
, как указано в rails docs, пройдя requires_new: true
User.transaction do
User.create(username: 'Kotori')
User.transaction(requires_new: true) do
User.create(username: 'Nemu')
raise ActiveRecord::Rollback
end
end
Что, как говорят документы: создает только "Котори". так как мощный ребенок "Нему" решил умереть молча.
Подробнее о Вложенные правила транзакций (oracle docs)
Update:
Чтобы лучше понять, почему rails nested transactions
работает таким образом, вам нужно знать немного больше о том, как вложенные транзакции работают на уровне DB, я цитирую из rails api docs:
Большинство баз данных не поддерживают истинные вложенные транзакции... Для того чтобы обойти эту проблему, #transaction будет эмулировать эффект вложенных транзакций, используя точки сохранения: http://dev.mysql.com/doc/refman/5.0/en/savepoint.html
Хорошо, тогда документы описывают поведение a nested transaction
в двух упомянутых случаях следующим образом:
В случае вложенного вызова # transaction будет вести себя следующим образом:
Блок будет работать без каких-либо действий. Все операторы базы данных, которые происходят внутри блока, эффективно добавляются к уже открытой транзакции базы данных.
Однако, если: require_new установлено, блок будет завернут в точку сохранения базы данных, действующую как суб-транзакция.
Я представляю себе осторожность, только представьте, что:
(1) ( без require_new) есть ли в случае, если вы использовали СУБД, полностью поддерживающую nested transactions
, или вы довольны "поддельным" поведением nested_attributes
в то время как опция (2) предназначена для поддержки обходного пути savepoint
, если вы этого не сделаете.
require_new
, если альтернативой является то, что откат ничего не делает - по умолчанию.
Это связано с взаимодействием, поскольку transaction do
блокирует специфические обработчики исключений ActiveRecord::Rollback
, которые возникают в этих блоках, и как Rails объединяет вложенные блоки transaction do
по умолчанию.
Рельсы transaction do
блоки имеют несколько другое поведение в зависимости от типа исключения, вызванного внутри них:
ActiveRecord::Rollback
исключение возникает в блоке transaction do
, эти исключения сохраняются блоком transaction do
и не выходят дальше.transaction do
и продолжают выходить за пределы этого блока.По умолчанию Rails объединяет вложенные транзакции вместе. Это означает, что транзакция будет прервана только тогда, когда у самой внешней транзакции возникнет пузырь.
Вместе эти два поведения означают, что когда a ActiveRecord::Rollback
возникает внутри вложенной транзакции, он вызывается внутренним блоком transaction do
и не восстанавливается повторно; внешний блок transaction do
, поскольку он не получает исключения, завершается успешно.
Чтобы подчеркнуть, что если вы создаете любое исключение, отличное от ActiveRecord::Rollback
, оно будет продолжать пузыриться через несколько блоков transaction do
, и внешняя транзакция будет прервана, как ожидалось.
Как уже упоминалось в другом месте, вы можете заставить вложенные транзакции Rails не "присоединяться" к своему родителю с помощью transaction(requires_new: true) do
; а также принуждать родительские транзакции не присоединяться к детям с помощью transaction(joinable: false) do
. Было рекомендуется, чтобы всегда использовать как transaction(joinable: true, requires_new: true) do
transaction(joinable: false, requires_new: true)