トランザクションとは
トランザクションとは複数の処理をまとめて1つの大きな処理として扱う機能です。
複数のリソースにまたがった変更をひとまとまりで扱う時、処理の1つで例外が発生したら、複数の処理を巻き戻すことができます。
トランザクションを適切に使えば、致命的なデータの不整合性防ぐ
ことができるのです。
#トランザクションはなぜ必要になるのか?
トランザクションがなぜ必要になるのかの理解を深める為に、ここではをよく使われている例の銀行口座への振込を使って説明したいと思います。
残高が10000円のAさん、残高が20000円のBさんがいるとしましょう。
AさんはBさんに5000円振り込むとします。
その時は以下の2つの処理から構成されます。
1、 Aさんの口座から5000円差し引き、残高が10000円 - 5000円 = 5000円になる
2、Bさんの口座に5000円プラスされ、残高が20000円 + 5000円 = 25000円になる
ですが、この2つの処理は振込という1つの処理として考えるべきでしょう。
なぜなら、2つの処理を分離して考えてしますと、Aさんが5000円送金した直後にシステムトラブルが起きた時以下のようになってします為です。
1、 Aさんの口座から5000円差し引き、残高が10000円 - 5000円 = 5000円になる
----------------------システムトラブル発生----------------------
2、Bさんの口座にお金お金が振り込まれない
このような事態にならない為にも、2つの処理を1つの振込として処理することにより、仮にAさんが5000円を振り込んだ後にシステムトラブルが発生したとしても1つの振込としての処理は失敗したことになり、最初から振込がなかったことにすることで5000円の消失を防ぐことができるのです。
このような場合に、複数の処理をまとめて1つの大きな処理として扱うトランザクションは必要となるのです。
Railsでの使用方法
transactionメソッドの基本構文
transactionメソッドの基本的な書き方は以下の通りです。とてもシンプルな構造です。
ActiveRecord::Base.transaction do
# 例外が発生するかもしれない処理
end
# 例外が発生しなかった場合の処理
rescue => e
# 例外が発生した場合の処理
使用方法
ここではPayment
(入金)、PaymentHistory
(入金履歴)のModelが存在するとしましょう。
入金があると入金履歴も作成する処理を見ていきます。
以下はtransactionブロックを使わず実装した処理です。
def create
payment = Payment.new(
amount: 1000
)
payment.save!
payment_history = PaymentHistory.new(
amount: 1000,
payment_day: Time.zone.now.strftime('%Y/%m/%d/%H時%M分%S秒')
)
payment_history.save!
end
このまま実行した場合、もしPaymentHistory
作成時に何らかの例外が発生してしまうと入金は行なったが、入金履歴が無いというデータの不整合性が発生してしまいます。
実際に例外が発生した場合のデータを見て見ましょう。
mysql > SELECT * FROM payments;
+-----+--------+
| id | amount |
+-----+--------+
| 1 | 1000 |
+-----+--------+
mysql > SELECT * FROM payment_histories;
Empty set (0.00 sec)
上記の通り、Paymentのレコードが作成されており、PaymentHistoryのレコードは存在しないことが分かります。
上記のようなデータの不整合性を避ける為にも、transactionブロックを活用すると全処理が成功
または処理が失敗しキャンセル
のどちらかに処理結果を限定できる。
以下はtransactionブロックを使用し、実装した処理です。
def create
ActiveRecord::Base.transaction do
payment = Payment.new(
amount: 1000
)
payment.save! # まだコミットされない
payment_history = PaymentHistory.new(
amount: 1000,
payment_day: Time.zone.now.strftime('%Y/%m/%d/%H時%M分%S秒')
)
payment_history.save! # まだコミットされない
end
# transactionブロックを正常に抜けるとコミットされる
end
transactionブロックを使用したコードの場合、transactionブロック内の処理が例外なく完了すれば、コミットされ登録成功となる。
下記はその時のデータです。
mysql > SELECT * FROM payments;
+-----+--------+
| id | amount |
+-----+--------+
| 1 | 1000 |
+-----+--------+
mysql > SELECT * FROM payment_histories;
+-----+--------+--------------------------+
| id | amount | payment_day |
+-----+--------+--------------------------+
| 1 | 1000 | "2019/07/15/06時35分10秒" |
+-----+--------+--------------------------+
一方、transactionブロック内で例外が発生した場合はロールバックされ、途中まで成功していた処理もキャンセルされる。
下記はその時のデータです。
mysql > SELECT * FROM payments;
Empty set (0.00 sec)
mysql > SELECT * FROM payment_histories;
Empty set (0.00 sec)
中途半端な更新も良しとする要件もある為、一概には言えないがこのように、複数のsave!
メソッドを使用する場合はtransactionブロックを使用するのが良いでしょう。
まとめ
というわけで、トランザクションについて説明していきました。
トランザクションは、複数のリソースにまたがった変更をひとまとまりで扱う時、処理の1つで例外が発生したら、複数の処理を巻き戻すことができます。
予期せぬバグを未然に防げるので、使えそうな場面では一連の処理にトランザクションを導入することを検討しましょう。