排他制御には悲観的ロックと楽観的ロックがあります。
悲観的ロック:
自分が操作している情報(レコード)は他人も操作する可能性がある
という観点から施される排他制御方法です。
具体例としては、あるレコードを操作する前に行ロックをかけてから参照・更新し、終了後にロックを解除する方法が取られるみたいです。
楽観的ロック:
自分が操作している情報(レコード)はまず他人は操作することがない
という観点から施される排他制御方法です。
具体例としては、あるレコードを読み込んだときに、当該レコードの最終更新時刻を取得し、自分が更新するときにその更新時刻が変更されていた場合にロールバックする方法が取られます。
ただ、悲観的ロックを掛けた場合、他のトランザクションがロックの解放待ちになるため、スループットが低下すると言われています。
一般的なシステムでは楽観的ロックで十分と言われているので、楽観的ロックを掛けたいと思います。Ruby on RailsのActiveRecordでは、簡単に楽観的ロックを実現する方法があるらしいので、今回はそれを採用。
ロックを掛けたいテーブルに以下のカラムを追加します。
カラム名: lock_version
カラム型: integer
初期値: 0 (← あったほうが無難だそう。)
追加するだけで、Railsが「こいつは楽観的ロックを掛けたいんだな~」っていうのを自動的に判断してくれて、勝手に排他制御してくれるみたいです。スバラシイ!!
例えばあるサイトの会員情報を管理するテーブルusersにカラムを追加する場合、以下のようなマイグレーションファイルを用意します。
====================================================
class AddLockVersionToUsers < ActiveRecord::Migration
def self.up
add_column :users, :lock_version, :integer,
:null => false, :default => 0
end
def self.down
remove_column :users, :lock_version
end
end
=====================================================
んでもって、マイグレート実行。
これで、もしレコードの更新時に他のトランザクションで先に更新されていた場合、エラーとなりロールバックされるようになります。
=====================================================
def update
@user = User.find(params[:id])
begin
User.transaction do
@user.update_attributes!(params[:user])
redirect_to(@user)
end
rescue ActiveRecord::StaleObjectError
flash[:error] = "他の人によって変更されました。"
render :action => 'edit'
end
end
=====================================================
ここでポイントいくつか。
1.lock_versionを定義したモデル(ここではUser)のtransactionメソッドにコードブロックを渡してあげること。
2.ブロック中で例外が発生するように、save!とかupdate_attributes!とか「!」つきのメソッドで更新すること。
3.発生するエラーは、"ActiveRecord::StaleObjectError"。
すると、更新するときに
UPDATE users
SET `lock_version` = 1, `updated_at` = '2010-07-09 XX:XX:XX'
WHERE id = 1
AND `lock_version` = 0
(usersの他のカラムについては省略。)
とか勝手にやってくれます。失敗するとExceptionを投げるっていうね。
とても便利ですね~。
参考URL → http://blogs.embarcadero.com/teamj/2009/01/31/369/
悲観的ロック:
自分が操作している情報(レコード)は他人も操作する可能性がある
という観点から施される排他制御方法です。
具体例としては、あるレコードを操作する前に行ロックをかけてから参照・更新し、終了後にロックを解除する方法が取られるみたいです。
楽観的ロック:
自分が操作している情報(レコード)はまず他人は操作することがない
という観点から施される排他制御方法です。
具体例としては、あるレコードを読み込んだときに、当該レコードの最終更新時刻を取得し、自分が更新するときにその更新時刻が変更されていた場合にロールバックする方法が取られます。
ただ、悲観的ロックを掛けた場合、他のトランザクションがロックの解放待ちになるため、スループットが低下すると言われています。
一般的なシステムでは楽観的ロックで十分と言われているので、楽観的ロックを掛けたいと思います。Ruby on RailsのActiveRecordでは、簡単に楽観的ロックを実現する方法があるらしいので、今回はそれを採用。
ロックを掛けたいテーブルに以下のカラムを追加します。
カラム名: lock_version
カラム型: integer
初期値: 0 (← あったほうが無難だそう。)
追加するだけで、Railsが「こいつは楽観的ロックを掛けたいんだな~」っていうのを自動的に判断してくれて、勝手に排他制御してくれるみたいです。スバラシイ!!
例えばあるサイトの会員情報を管理するテーブルusersにカラムを追加する場合、以下のようなマイグレーションファイルを用意します。
====================================================
class AddLockVersionToUsers < ActiveRecord::Migration
def self.up
add_column :users, :lock_version, :integer,
:null => false, :default => 0
end
def self.down
remove_column :users, :lock_version
end
end
=====================================================
んでもって、マイグレート実行。
これで、もしレコードの更新時に他のトランザクションで先に更新されていた場合、エラーとなりロールバックされるようになります。
=====================================================
def update
@user = User.find(params[:id])
begin
User.transaction do
@user.update_attributes!(params[:user])
redirect_to(@user)
end
rescue ActiveRecord::StaleObjectError
flash[:error] = "他の人によって変更されました。"
render :action => 'edit'
end
end
=====================================================
ここでポイントいくつか。
1.lock_versionを定義したモデル(ここではUser)のtransactionメソッドにコードブロックを渡してあげること。
2.ブロック中で例外が発生するように、save!とかupdate_attributes!とか「!」つきのメソッドで更新すること。
3.発生するエラーは、"ActiveRecord::StaleObjectError"。
すると、更新するときに
UPDATE users
SET `lock_version` = 1, `updated_at` = '2010-07-09 XX:XX:XX'
WHERE id = 1
AND `lock_version` = 0
(usersの他のカラムについては省略。)
とか勝手にやってくれます。失敗するとExceptionを投げるっていうね。
とても便利ですね~。
参考URL → http://blogs.embarcadero.com/teamj/2009/01/31/369/