Почему Rails игнорирует откат в (псевдо) вложенной транзакции?

15

В соответствии с документами 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", но почему он не вызывает откат для родителя? Скрывает ли дочерняя транзакция откат?

Другими словами, почему не существует способа отката родительской транзакции из внутри вложенного дочернего элемента?

  • 0
    Посмотрите этот вопрос для примера того, как обойти это поведение: stackoverflow.com/questions/20926873/…
Теги:
transactions
activerecord

2 ответа

13
Лучший ответ

На самом деле это именно то, как было разработано Вложенные транзакции. Я цитирую документы 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, если вы этого не сделаете.

  • 4
    Но зачем вообще требовать использования require_new , если альтернативой является то, что откат ничего не делает - по умолчанию.
  • 0
    @MikeManfrin обновил мой ответ
Показать ещё 3 комментария
0

Это связано с взаимодействием, поскольку 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

  • 0
    Вы имеете в виду transaction(joinable: false, requires_new: true)
  • 0
    @IonutPopa Спасибо! Я исправил свой ответ.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню