トランザクションやロックについて - lanchester/rails_environment GitHub Wiki

トランザクション

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ブロックを使用する。
  • with_lockはトランザクションも開始してくれるので別途transactionを記述する必要は無い
account = Account.find(10)
account.with_lock do
  account.name = 'fuga'
  account.save!
end
  • 他のトランザクションがロックの解放待ちになるため、長く待たされるとタイムアウトになってしまう可能性もある。
  • 利用できるDBMSはPostgresかMySQL。