トランザクション
begin
ActiveRecord::Base.transaction do
.
.
raise 'ロールバックします'
end
p 'コミット' # トランザクション処理を確定
rescue => e
p 'ロールバック' # トランザクション処理を戻す
end
-
transactionブロックの中で登録・更新処理を行う場合は、saveやupdateではなく、
save!
,update!
を使用する。 -
transactionブロックの中で複数のモデルの更新を行った後に例外を発生させると、全部のモデルがロールバックする。
楽観的ロック
「競合は多分起きないだろう」という前提で、データの取得時には何もせず、更新時に競合をチェックする方法。
- レコードのバージョン管理を行うため、テーブルに
lock_version
カラムを追加する。その際、デフォルト値を0にする。 - lock_versionはレコード更新時に1増える。
- lock_versionはフォームのパラメータとして送信する必要がある(hiddenでOK)。
- 更新時に、データ取得時のlock_versionと異なればエラー(ActiveRecord::StaleObjectError)を発生させる。
- 設定ファイルで
ActiveRecord::Base.lock_optimistically = false
にすると、楽観的ロックを無効にすることもできる。
悲観的ロック
「競合が起きるだろう」という前提で、データの取得時にデータをロックし、更新時にロックを解除する方法。「これからこのデータをいじるから、他の人はこっちの処理が終わるまで待ってね」ということ。
- トランザクションを使用する。
上記参照。
- モデルの
lock
メソッドを使用する。
User.lock.find(1)
user = User.first
user.lock!
- モデルの
with_lock
ブロックを使用する。
account = Account.find(10)
account.with_lock do
account.name = 'fuga'
account.save!
end
- 他のトランザクションがロックの解放待ちになるため、長く待たされるとタイムアウトになってしまう可能性もある。
- 利用できるDBMSはPostgresかMySQL。