悲観的ロックについてよく知らなかったので調べました。
前提条件
Rails 4.2.4
PostgreSQL 9.4
使い方
find
する前にlock
をつけるだけ。FOR UPDATE
というロック処理句が発行される。
# SELECT * FROM accounts WHERE id=1 FOR UPDATE
Account.lock.find(1)
トランザクションがはられている間ロックする。
Account.transaction do
account = Account.lock.first
account.balance -= 100
account.save!
end
ロックとトランザクションを同時にすることもできる。
account = Account.first
account.with_lock do
account.balance -= 100
account.save!
end
動作確認
rails consoleを2つ用意する。
console 1
account = Account.first
account.with_lock do
sleep 30
end
console 2
Account.lock.first
なお、console 2でhoge.first
とした場合はレコードを取得できるがhoge.first.update(...)
としたときはロックが解除されるのを待つ。
というのも、
行レベルロックは、データの問い合わせには影響を与えません。 行レベルロックは、同じ行に対する書き込みとロックだけをブロックします。
という仕様のためらしい。
注意
同一トランザクション内でロックしながらいろいろレコードいじったりするとデッドロックになる可能性があるので注意したほうがよさそう。
参考
- http://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html
- https://www.postgresql.jp/document/9.4/html/sql-select.html#SQL-FOR-UPDATE-SHARE
- https://www.postgresql.jp/document/9.4/html/explicit-locking.html#LOCKING-ROWS
- http://stackoverflow.com/questions/21404484/rails-3-how-to-simply-test-pessimistic-locking-on-console (情報が古いので注意)