- 確認した環境
- Ruby 2.5.1
- Rails 5.2.1
- MySQL 5.7.27
腕試しに以下のスクリプトを実行した場合、最終行の User.first.name
は何が得られるか考えてみてください.
User.first.update!(name: 'Alice')
ActiveRecord::Base.transaction do
User.first.update!(name: 'Bob')
ActiveRecord::Base.transaction do
User.first.update!(name: 'Carol')
raise ActiveRecord::Rollback
end
end
User.first.name
正解はここをクリック
User.first.name
の結果は Carol になります.
正解しましたか?
正解する人もいると思いますが直感と違うかと思います.
TL;DR
- 可能ならトランザクションをネストしない設計する
- トランザクションをネストする設計の際は
requires_new: true
を使う- 付けないと思いのよらないゴミができる可能性がある
検証
分かりやすくするため、SQLをコメントに加えました.
User.first.update!(name: 'Alice')
# BEGIN
# UPDATE `users` SET `name` = 'Alice' WHERE `users`.`id` = 1
# COMMIT
ActiveRecord::Base.transaction do
# BEGIN
User.first.update!(name: 'Bob')
# UPDATE `users` SET `name` = 'Bob' WHERE `users`.`id` = 1
ActiveRecord::Base.transaction do
User.first.update!(name: 'Carol')
# UPDATE `users` SET `name` = 'Carol' WHERE `users`.`id` = 1
raise ActiveRecord::Rollback
end
end
# COMMIT
User.first.name # => 'Carol'
requires_new を使った場合
いくつかのRDBにはトランザクションにSAVEPOINTをサポートしているものがあります.
このSAVEPOINTを利用するには requires_new
を指定することで有効になり直感に近い挙動をする様になります.
User.first.update!(name: 'Alice')
# BEGIN
# UPDATE `users` SET `name` = 'Alice' WHERE `users`.`id` = 1
# COMMIT
ActiveRecord::Base.transaction do
# BEGIN
User.first.update!(name: 'Bob')
# UPDATE `users` SET `name` = 'Bob' WHERE `users`.`id` = 1
ActiveRecord::Base.transaction(requires_new: true) do
# SAVEPOINT active_record_1
User.first.update!(name: 'Carol')
# UPDATE `users` SET `name` = 'Carol' WHERE `users`.`id` = 1
raise ActiveRecord::Rollback
end
# ROLLBACK TO SAVEPOINT active_record_1
end
# COMMIT
User.first.name # => 'Carol'