ネストしたトランザクション内で ActiveRecord::Rollback を raise しても握りつぶされるだけだ

こんにちは、三苫です。

ActiveRecordのトランザクションについてちょっとしたtipsです。

以下のActiveRecordを使ったrubyコードがどのようなクエリを発行するだろう?

おそらく多数の人が期待する動作はこう(以下、クエリ例はPostgreSQLのものです)

実際の動作はこう!

世間は厳しい!!
全ロールバックしたければ書くべきコードはこう!!!!

ネストしたトランザクションブロックだけロールバックしたければ書くべきコードはこう!

その際のクエリはこう!

では、このわかりにくい挙動、ドキュメンテーションされているのかどうか。

もちろんドキュメンテーションされています。ドキュメントを読まずに思い込みで実装してはいけない。
特に、トランザクションのようなセンシティブな問題は・・・。トランザクションが必要とされる処理はセンシティブ・・・。

ドキュメントは英語なので訳したものを以下に記載します。
上で書いたことがほぼそのまま書かれています。最初からドキュメントを読んでいれば・・・。

該当箇所は以下です。
https://github.com/rails/rails/blob/v4.2.0/activerecord/lib/active_record/transactions.rb#L142
訳が間違ってる可能性もあるので、微妙に感じた場合は是非原文を読みましょう。

Nested transactions
(ネストしたトランザクション)

+transaction+ calls can be nested. By default, this makes all database
statements in the nested transaction block become part of the parent
transaction. For example, the following behavior may be surprising:

トランザクションメソッドはネストして呼ぶことができる。
デフォルトでは、ネストしたトランザクション内でのステートメントはすべて親トランザクションの一部として実行される。
以下の例は、もしかしたら驚くかもしれない。(訳注:そら驚くやろ!)

creates both "Kotori" and "Nemu". Reason is the <tt>ActiveRecord::Rollback</tt>
exception in the nested block does not issue a ROLLBACK. Since these exceptions
are captured in transaction blocks, the parent block does not see it and the
real transaction is committed.

上記の例ではKotoriとNemuが作成される。理由は ActiveRecord::Rollback はネストしたトランザクションブロックをロールバックしないからだ。
ActiveRecord::Rollbackのような例外はトランザクションブロックの中で捕捉され、親のトランザクションブロックからは検知できないし、実際のトランザクションはコミットされる。

In order to get a ROLLBACK for the nested transaction you may ask for a real
sub-transaction by passing <tt>requires_new: true</tt>. If anything goes wrong,
the database rolls back to the beginning of the sub-transaction without rolling
back the parent transaction. If we add it to the previous example:

ネストしたトランザクションをロールバックするためには、requires_new: true を指定して実際のサブトランザクションとしなければならない。
もし、何か良くないことが起きたときにサブトランザクションだけをロールバックし、親トランザクションをロールバックしたくないときは前の例を以下のように修正すればよい。

only "Kotori" is created. This works on MySQL and PostgreSQL. SQLite3 version >= '3.6.8' also supports it.

Kotoriだけが作成される。これはMySQLとPostgreSQLで動作する。SQLite3では 3.6.8 以上でサポートされる。

Most databases don't support true nested transactions. At the time of
writing, the only database that we're aware of that supports true nested
transactions, is MS-SQL. Because of this, Active Record emulates nested
transactions by using savepoints on MySQL and PostgreSQL. See
http://dev.mysql.com/doc/refman/5.6/en/savepoint.html
for more information about savepoints.

ほとんどのデータベースはネストしたトランザクションをサポートしない。
このドキュメントを書いている時点ではネストしたトランザクションをサポートしているのはMS-SQLだけだ。
なので、ActiveRecordはネストしたMySQLとPostgreSQLにおいてネストしたトランザクションをSAVEPOINTを利用してエミュレートしている。
SAVEPOINTについての情報はMySQLの以下のドキュメントに詳しい。
http://dev.mysql.com/doc/refman/5.6/en/savepoint.html
(訳注:日本語でPostgreSQLの場合はこちら参照 http://www.postgresql.jp/document/9.3/html/sql-savepoint.html

Comments are closed, but you can leave a trackback: Trackback URL.