ActiveRecord::Base.transaction はネストしたトランザクションに対応しているのか確認した。
- 2013/03/13 (Wed) |
- Ruby on Rails |
- CM(0) |
- Edit |
- ▲Top
細かい説明は抜きにして、以下のirb (rails console -s で起動するコンソールモード)での実行内容を見てみよう。
まずはUserというモデルをsaveメソッド単体で保存してみる。
見ての通り、saveメソッドは単体でトランザクションも起動している。
では今度はトランザクションをネストさせてsaveを使ってみる。
saveメソッドの内部にもトランザクション処理が起きて、いわゆるトランザクションのネストが発生するかと思ったが、SQLレベルでBeginが2回生成されていないこと、警告(筆者の環境ではDBがPostgreSQLなのでBEGINが2回続くと警告が出る)が出ないところから見ると、saveメソッド内でトランザクションを開始してはいないようだ。
では明示的にトランザクションをネストさせたらどうなるだろうか
ご覧のとおり、トランザクションはネストしない。
このことはメソッドを介しても全く同様。
このように、ActiveRecord::Base.transactionを明示して行うトランザクション処理ではネストが発生しない。
そのためロールバックも非常に明確となる。
ActiveRecord::Base.transaction のブロック中に、ActiveRecord::Base.transactionブロックをネストしても見た目とは反対に実際にはトランザクションがネストしないので、一番ネストの深いブロックで例外が発生しても、全体としてはBEGIN => Rollback の制御が1回起きるだけだ。
以下が論より証拠である。
上記通りにActiveRecord::Base.transactionをに使う限りにおいては、コード上でトランザクションブロックをいくらネストさせても、どこかで例外が発生した時点で全ての処理がロールバックされ、セーブポイント等は考慮されない。
セーブポイントが重要という場合は困るだろうが、トランザクションの一意性を考えるとロールバックと同時にトランザクション内全てのレコード処理が自動で無効になってくれたほうが親切だと言えるだろう。これで心置きなくトランザクションを安心して使えるというわけだ。
まずはUserというモデルをsaveメソッド単体で保存してみる。
[root@localhost base]# rails console -s
Loading development environment in sandbox (Rails 4.0.0.beta1)
Any modifications you make will be rolled back on exit
irb(main):001:0> a = User.new
=> #
irb(main):002:0> a.user_no = "123"
=> "123"
irb(main):003:0> a.user_name = "aiueo"
=> "aiueo"
irb(main):004:0> a.save
(0.3ms) BEGIN
User Exists (1.6ms) SELECT 1 AS one FROM "users" WHERE "users"."user_no" = '123' LIMIT 1
SQL (13.1ms) INSERT INTO "users" ("updated_at", "user_name", "user_no") VALUES ($1, $2, $3, $4) RETURNING "id" [ ["created_at", Wed, 13 Mar 2013 15:10:12 UTC +00:00], ["updated_at", Wed, 13 Mar 2013 15:10:12 UTC +00:00],["user_name", "aiueo"], ["user_no", "123"]]
(3.3ms) COMMIT
=> true
見ての通り、saveメソッドは単体でトランザクションも起動している。
では今度はトランザクションをネストさせてsaveを使ってみる。
irb(main):061:0> ActiveRecord::Base.transaction do
irb(main):062:1> a = User.new
irb(main):063:1> a.user_no = "234"
irb(main):064:1> a.user_name = "aiueo"
irb(main):065:1> a.user_category_id = 1
irb(main):066:1> a.user_qualification_id = 1
irb(main):067:1> a.card_registered_date = "20130313"
irb(main):068:1> a.library_id = 1
irb(main):069:1> a.save
irb(main):070:1> end
(0.3ms) BEGIN
User Exists (1.2ms) SELECT 1 AS one FROM "users" WHERE "users"."user_no" = '234' LIMIT 1
SQL (2.1ms) INSERT INTO "users" ("created_at", "updated_at", "user_name", "user_no") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Wed, 13 Mar 2013 15:41:55 UTC +00:00], ["updated_at", Wed, 13 Mar 2013 15:41:55 UTC +00:00], ["user_name", "aiueo"], ["user_no", "234"]]
(3.0ms) COMMIT
=> true
saveメソッドの内部にもトランザクション処理が起きて、いわゆるトランザクションのネストが発生するかと思ったが、SQLレベルでBeginが2回生成されていないこと、警告(筆者の環境ではDBがPostgreSQLなのでBEGINが2回続くと警告が出る)が出ないところから見ると、saveメソッド内でトランザクションを開始してはいないようだ。
では明示的にトランザクションをネストさせたらどうなるだろうか
irb(main):072:0> ActiveRecord::Base.transaction do
irb(main):073:1* a = User.new
irb(main):074:1> a.user_no = "345"
irb(main):075:1> a.user_name = "aiueo"
irb(main):080:1> a.save
irb(main):081:1>
irb(main):082:1* ActiveRecord::Base.transaction do
irb(main):083:2* b = User.new
irb(main):084:2> b.user_no = "456"
irb(main):085:2> b.user_name = "aiueo"
irb(main):090:2> b.save
irb(main):091:2> end
irb(main):092:1> end
(0.3ms) BEGIN
User Exists (0.9ms) SELECT 1 AS one FROM "users" WHERE "users"."user_no" = '345' LIMIT 1
SQL (2.9ms) INSERT INTO "users" ("created_at", "updated_at", "user_name", "user_no") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Wed, 13 Mar 2013 15:53:26 UTC +00:00],["updated_at", Wed, 13 Mar 2013 15:53:26 UTC +00:00], ["user_name", "aiueo"], ["user_no", "345"]]
User Exists (0.8ms) SELECT 1 AS one FROM "users" WHERE "users"."user_no" = '456' LIMIT 1
SQL (1.3ms) INSERT INTO "users" ("created_at", "updated_at", "user_name", "user_no") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Wed, 13 Mar 2013 15:53:26 UTC +00:00], ["updated_at", Wed, 13 Mar 2013 15:53:26 UTC +00:00],["user_name", "aiueo"], ["user_no", "456"]]
(3.5ms) COMMIT
=> true
ご覧のとおり、トランザクションはネストしない。
このことはメソッドを介しても全く同様。
irb(main):094:0> def test
irb(main):095:1> ActiveRecord::Base.transaction do
irb(main):096:2* a = User.new
irb(main):097:2> a.user_no = "789"
irb(main):098:2> a.user_name = "aiueo"
irb(main):103:2> a.save
irb(main):104:2> end
irb(main):105:1> end
=> nil
irb(main):106:0> ActiveRecord::Base.transaction do
irb(main):107:1* a = User.new
irb(main):108:1> a.user_no = "890"
irb(main):109:1> a.user_name = "aiueo"
irb(main):114:1> a.save
irb(main):115:1>
irb(main):116:1* test
irb(main):117:1> end
(0.4ms) BEGIN
User Exists (0.5ms) SELECT 1 AS one FROM "users" WHERE "users"."user_no" = '890' LIMIT 1
SQL (0.5ms) INSERT INTO "users" "created_at","updated_at", "user_name", "user_no") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Wed, 13 Mar 2013 16:02:05 UTC +00:00], ["updated_at", Wed, 13 Mar 2013 16:02:05 UTC +00:00],["user_name", "aiueo"], ["user_no", "890"]]
User Exists (0.3ms) SELECT 1 AS one FROM "users" WHERE "users"."user_no" = '789' LIMIT 1
SQL (0.3ms) INSERT INTO "users" "created_at", "updated_at","user_name", "user_no") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Wed, 13 Mar 2013 16:02:05 UTC +00:00], ["updated_at", Wed, 13 Mar 2013 16:02:05 UTC +00:00],["user_name", "aiueo"], ["user_no", "789"]]
(1.1ms) COMMIT
=> true
このように、ActiveRecord::Base.transactionを明示して行うトランザクション処理ではネストが発生しない。
そのためロールバックも非常に明確となる。
ActiveRecord::Base.transaction のブロック中に、ActiveRecord::Base.transactionブロックをネストしても見た目とは反対に実際にはトランザクションがネストしないので、一番ネストの深いブロックで例外が発生しても、全体としてはBEGIN => Rollback の制御が1回起きるだけだ。
以下が論より証拠である。
irb(main):001:0> ActiveRecord::Base.transaction do
irb(main):002:1* a = User.new
irb(main):003:1> a.user_no = "901"
irb(main):004:1> a.user_name = "aiueo"
irb(main):009:1> a.save
irb(main):010:1>
irb(main):011:1* ActiveRecord::Base.transaction do
irb(main):012:2* b = User.new
irb(main):013:2> b.user_no = "012"
irb(main):014:2> b.user_name = "aiueo"
irb(main):019:2> b.save
irb(main):020:2> raise "Exception occured."
irb(main):021:2> end
irb(main):022:1> end
(0.1ms) BEGIN
User Exists (0.8ms) SELECT 1 AS one FROM "users" WHERE "users"."user_no" = '901' LIMIT 1
SQL (5.5ms) INSERT INTO "users" ("created_at", "updated_at","user_name", "user_no") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Wed, 13 Mar 2013 16:20:52 UTC +00:00], ["updated_at", Wed, 13 Mar 2013 16:20:52 UTC +00:00],["user_name", "aiueo"], ["user_no", "901"]]
User Exists (0.3ms) SELECT 1 AS one FROM "users" WHERE "users"."user_no" = '012' LIMIT 1
SQL (0.3ms) INSERT INTO "users" ("created_at", "updated_at", "user_name", "user_no") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", Wed, 13 Mar 2013 16:20:52 UTC +00:00],["updated_at", Wed, 13 Mar 2013 16:20:52 UTC +00:00],["user_name", "aiueo"], ["user_no", "012"]]
(0.8ms) ROLLBACK
RuntimeError: Exception occured.
上記通りにActiveRecord::Base.transactionをに使う限りにおいては、コード上でトランザクションブロックをいくらネストさせても、どこかで例外が発生した時点で全ての処理がロールバックされ、セーブポイント等は考慮されない。
セーブポイントが重要という場合は困るだろうが、トランザクションの一意性を考えるとロールバックと同時にトランザクション内全てのレコード処理が自動で無効になってくれたほうが親切だと言えるだろう。これで心置きなくトランザクションを安心して使えるというわけだ。
PR
カレンダー
フリーエリア
最新CM
最新記事
(06/05)
(06/04)
(06/04)
(11/18)
(11/18)
ブログ内検索
最古記事
(09/15)
(09/20)
(09/27)
(09/27)
(10/11)
COMMENT