40
42

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

トランザクションとはなにか?どんな時に使うか? Railsでの使用方法

Last updated at Posted at 2019-07-14

トランザクションとは

トランザクションとは複数の処理をまとめて1つの大きな処理として扱う機能です。
複数のリソースにまたがった変更をひとまとまりで扱う時、処理の1つで例外が発生したら、複数の処理を巻き戻すことができます。
トランザクションを適切に使えば、致命的なデータの不整合性防ぐことができるのです。

#トランザクションはなぜ必要になるのか?
トランザクションがなぜ必要になるのかの理解を深める為に、ここではをよく使われている例の銀行口座への振込を使って説明したいと思います。

残高が10000円のAさん、残高が20000円のBさんがいるとしましょう。
AさんはBさんに5000円振り込むとします。

その時は以下の2つの処理から構成されます。

1、 Aさんの口座から5000円差し引き、残高が10000円 - 5000円 = 5000円になる

2、Bさんの口座に5000円プラスされ、残高が20000円 + 5000円 = 25000円になる

スクリーンショット 2019-07-15 3.45.50.png

ですが、この2つの処理は振込という1つの処理として考えるべきでしょう。
なぜなら、2つの処理を分離して考えてしますと、Aさんが5000円送金した直後にシステムトラブルが起きた時以下のようになってします為です。

1、 Aさんの口座から5000円差し引き、残高が10000円 - 5000円 = 5000円になる

  ----------------------システムトラブル発生----------------------

2、Bさんの口座にお金お金が振り込まれない

スクリーンショット 2019-07-15 4.26.29.png

このような事態にならない為にも、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つで例外が発生したら、複数の処理を巻き戻すことができます。

予期せぬバグを未然に防げるので、使えそうな場面では一連の処理にトランザクションを導入することを検討しましょう。

40
42
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
40
42

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?