ActiveRecord::Locking::Pessimisticで用意されているメソッドの使い方

この記事について

ActiveRecordのモジュールActiveRecord::Locking::Pessimisticについて調べたことと使い方についてまとめます。 Railsのバージョン5系で挙動を確認していて、ドキュメントは5.2.2を参考にしています。

ActiveRecord::Locking::Pessimisticを使うことでできるようになること

ActiveRecord::Locking::Pessimisticはロジックに悲観的ロックをかける機能を提供してくれます。 悲観的ロックをかけることによって、口座間の送金ロジックなど原子性が求められる(不可分でなければならない)処理の信頼をより担保してくれます。 ただトランザクションをかけるだけだと、コミット前の情報に干渉することはできないにせよ、selectでコミット前のデータを取ることはできてしまいます。 悲観的ロックをかけたレコードをselectで取りに行くと、トランザクションがcommitされるまで待ってくれるので、確実に最新のデータを取ってくることができるようになります。

ActiveRecord::Locking::Pessimisticで用意されているメソッド

# id = 1のレコードに悲観的ロックをかける
Account.lock.find(1)
Account.transaction do
  # select * from accounts where ...
  accounts = Account.where(...)
  account1 = accounts.detect { |account| ... }
  account2 = accounts.detect { |account| ... }
  # 同じく1レコードに悲観的ロックをかける
  # lockメソッドに例外処理などの機能を盛り込んだメソッド
  account1.lock!
  account2.lock!
  account1.balance -= 100
  account1.save!
  account2.balance += 100
  account2.save!
end
account = Account.first

# トランザクションの中でlock!メソッドを読んでくれる
account.with_lock do
  account.balance -= 100
  account.save!
end

上記はapiリファレンスからの抜粋なのですが、SQLの悲観的ロックを知らなかったため理解に手こずりました(今も概念的にしか理解できていない) transactionだけ貼っておけば原子性の求められる処理は安心!と考えていたのですが、transactionで処理中のデータに対してもselect文を走らせることができてしまうことを考慮に入れて、必要に応じて悲観的ロックを使っていきたいと思います。

参考資料

api.rubyonrails.org

dev.mysql.com