WEBサービス創造記

WEBサービスを作ったり保守したりしてる人のメモブログです。

ActiveRecordによる楽観的ロック

      2015/05/26

楽観的ロックについて

楽観的ロックはDBテーブル内の同一レコード更新のバッティングを防ぐための技術である。

楽観的ロックでは、複数のユーザが同一レコードをアクセスすることを想定する。
例えば、AさんとBさんがまったく同時刻に管理画面の更新フォームのページにアクセスしたとする。
Aさんが先に更新したにも関わらず、BさんがAさんの5分後に更新を行ったとしたらAさんの更新内容は失われる(二人共まったく同じタイミングで更新フォームのページにアクセスしたにも関わらず)。
楽観的ロックでは、先に更新したAさんの更新内容を保持し、Bさんに更新を行えないようにすることでデータの競合を防ぐ。

ActiveRecordでの楽観的ロック

ActiveRecordでは規約により、テーブルにlock_versionというカラムを追加すると楽観的ロックが有効になる。

既存テーブルへカラムを追加するか、

$ rails generate migration add_lock_version_to_users lock_version:integer

新しくテーブルを作成するときにこのカラムを組み込むかしておく。
lock_versionカラムは数値型で初期値を0とする。

class CreateUsers < ActiveRecord::Migration
  def self.up
    create_table :users do |t|
      t.string :name, :null => false
      t.integer :lock_version, :default => 0
    end
  end

  def self.down
    drop_table :users
  end
end

_form.erb.htmlなどのフォームがあるビューには以下のようにhidden_field_tagでlock_versionを渡すようにする。

<%= f.hidden_field :lock_version %>
<%= f.text_field :name %>

lock_versionカラムの存在によって、更新時にlock_versionの値が書き換わっていた場合はActiveRecord::StaleObjectError例外が発生するようになり、これをrescueすることでロールバックが行えるようになる。

def update
  @user = User.find(params[:id])
  begin
    User.transaction do
      @user.update_attributes!(params[:user])
      redirect_to(user_path(@user), :notice => t("flash.notice.update"))
    end
  rescue ActiveRecord::StaleObjectError
    flash[:error] = "既に別の場所で編集されています。"
    render :action => 'edit'
  end
end

なお、設定ファイルで ActiveRecord::Base.lock_optimistically = false を設定することでこの挙動を無効にすることもできる。

楽観的ロックと悲観的ロックについて

なお、楽観的ロックはレコードが複数のユーザに同時に更新されることはないだろうという状況で用いるものであり、逆に、レコードが複数のユーザに同時に更新される可能性があるということがわかりきっている場合は悲観的ロックを利用する。

 - Ruby on Rails , ,