Rails 3. Nested transactions. Exception in a child block

ぐ巨炮叔叔 提交于 2020-01-21 12:46:50

问题


Why doesn't ActiveRecord rollback changes in nested transactions after exception was risen in a child block?

Here are examples:

1.


>> Client.transaction do
?>   Client.create(:name => 'Pavel')
>>   Client.transaction do
?>     Client.create(:name => 'Elena')
>>     raise ActiveRecord::Rollback
>>   end
>> end
=> nil
>> Client.all.map(&:name)
=> ["Pavel", "Elena"] # instead of []

2.


>> Client.transaction do
?>   Client.create(:name => 'Pavel')
>>   Client.transaction(:requires_new => true) do
?>     Client.create(:name => 'Elena')
>>     raise ActiveRecord::Rollback
>>   end
>> end
=> nil
>> Client.all.map(&:name)
=> ["Pavel", "Elena"] # instead of ["Pavel"]

Thanks.

Debian GNU/Linux 5.0.6;

Ruby 1.9.2;

Ruby on Rails 3.0.1;

SQLite 3.7.3.


回答1:


I'm having the same problem, and I can duplicate your result exactly. If I raise ActiveRecord::Rollback in the outer block, then the whole transaction rolls back, but otherwise, nothing gets rolled back.

Apparently, the current version of ActiveRecord does not know how to do nested transactions with SQLite3, even though ActiveRecord is supposed to implement nested transactions using savepoints, and SQLite has supported savepoints since 3.6.8.

As further evidence that this is simply not supported by ActiveRecord yet, try this...

> List.connection.supports_savepoints?
=> false

Ubuntu 11.04 - the Natty Narwhal;

ruby 1.8.7 (2010-04-19 patchlevel 253) [i486-linux], MBARI 0x8770, Ruby Enterprise Edition 2010.02;

Ruby on Rails 3.0.3;

sqlite3 gem 1.3.3

SQLite 3.7.2;




回答2:


The rails transaction implementation does not make use of savepoints (or similar technologies) that are used by databases to support nested transactions. True nested transactions are not supoprted by the databases themselves.

example:

begin -- starts transaction 1
  begin -- start transaction 2

    insert into something (foo) values ('bar');

  commit -- ends transaction 1
rollback -- is ignored

The first commit or rollback always closes the out-most transaction.

There is a way how databases actually can do the nesting. this would use the before mentioned savepoints. example

begin -- starts transaction 1
  savepoint foo -- starts "transaction" 2

    insert into something (foo) values ('bar');

  release -- commit for transaction 2
rollback -- roll back the data of the savepoint and everything else within transaction 1

You can nest as many savepoints as you want within each other, as long as a transaction is open.

For rails itself there is a catch though: The functions create and similar wrap themselves within an transaction. so your first example produces the follwing sql

begin  -- transaction.do
  begin  -- Client.create
    insert into clients ( name ) values ('Pavel')  -- Client.create
  commit  -- Client.create, closes the out-most transaction
  begin -- transaction.do
    begin  -- Client.create
      insert into clients ( name ) values ('Elena')  -- Client.create
    commit  -- Client.create, closes the out-most transaction

So your Exception just arrives to late.

You can patch this issue, but you have to do it for every connection adapter.

PS: You might be confused by the -- within the sql. Those are single line comments in mysql..



来源:https://stackoverflow.com/questions/4153719/rails-3-nested-transactions-exception-in-a-child-block

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!