web-dev-qa-db-ja.com

Rails ActiveRecord-読み取り用にテーブルをロックするにはどうすればよいですか?

私は次のようなRails ActiveRecordコードを持っています:

new_account_number = Model.maximum(:account_number)
# Some processing that usually involves incrementing
# the new account number by one.
Model.create(foo: 12, bar: 34, account_number: new_account_number)

このコードはそれ自体で正常に機能しますが、DelayedJobワーカーによって処理されるバックグラウンドジョブがいくつかあります。 2人のワーカーがいて、両方がこのコードを処理するジョブのバッチの処理を開始すると、最大値を見つけてから新しいものを作成するまでの遅延のため、同じaccount_numberを持つ新しいModelレコードを作成することになります。さらに高いアカウント番号で記録します。

今のところ、データベースレベルで一意性制約をモデルテーブルに追加し、この制約が例外をトリガーする場合に備えて最大値を再選択して再試行することで、これを解決しました。

しかし、それはハックのように感じます。

account_number列にデータベースレベルで自動インクリメントを追加することはオプションではありません。これは、account_numberの割り当てには単なるインクリメント以上のものが伴うためです。

理想的には、読み取りのために問題のテーブルをロックしたいので、完了するまで他の人はテーブルに対して最大選択クエリを実行できません。しかし、どうすればいいのかわかりません。私はPostgresqlを使用しています。

16
Niels B.

ActiveRecord :: Locking docs に基づくRailsはテーブルレベルのロック用の組み込みAPIを提供していません。

ただし、生のSQLを使用してこれを行うことはできます。 Postgresの場合、これは次のようになります

ActiveRecord::Base.transaction do
  ActiveRecord::Base.connection.execute('LOCK table_name IN ACCESS EXCLUSIVE MODE')
  ...
end

ロックはトランザクション内で取得する必要があり、トランザクションが終了すると自動的に解放されます。

ここで使用するSQLは、データベースによって異なることに注意してください。

13
Max Wallace

テーブル全体をロックする方法についてはすでに回答がありますが、それは避けてください。代わりに、アドバイザリーロックを確認する必要があると思います。他のビジネスのためにテーブルを開いたまま、同じコードブロックが2台のマシンで同時に実行されないようにします。

それはまだデータベースを使用しますが、テーブルをロックしません。

「with_advisory_lock」というgemは次のように使用できます。

Model.with_advisory_lock("ADVISORY_LOCK_NAME") do
  # Your code
end

https://github.com/ClosureTree/with_advisory_lock

SQLiteでは動作しません。

8
kaspernj

一意の制約を設定するISハックではありません。データの一貫性を保つためのものです。ちなみに、ここにはさらにいくつかのオプションがあります。

  1. SELECT FOR UPDATEまたはPostreSQLのアドバイザリロックを使用して、一部のDBリソースをロックします(たとえば、一意のレコードである可能性があります)( docs を参照)。

  2. シーケンスを使用します( docs )。

2つのアプローチの主な違いは、他のセッションがトランザクションのコミットを待機し、#2が許可するため、#1では数値にギャップがないことです。

6

一度に1つのプロセスのコードをロックするために、ホールテーブルをロックする必要はありません。テーブル全体をロックすると、パフォーマンスの問題が発生します。「with_lock」メソッドを使用すると、同じ行を常に1つロックできます。これにより、コードが完全に保護されます。余分な宝石は必要ありません。また、トランザクションを作成します。このような: m = Model.order(:id).first m.with_lock do #aquire lock #some code here for a single process at a time
end #release lock

0
Ehud

ActiveRecord :: Locking :: Pessimistic からlockメソッドを使用できます。

0
Helio Santos