私は約500アカウントを持つテーブルを持っている状況があります。これらのアカウントは、一部の金融取引の提出を行うために使用されています。トランザクションの開始が、私はこの表からアカウントを取得する必要がある場合には(任意のランダムなアカウントが働くだろう)と動作が終了するまで、他の行は、このアカウントへのアクセス権を持ちませんので、その行をロックします。その後、外部システムへのトランザクションを提出して、アカウントのロックを解除します。
私は、次のアルゴリズムを使用して、これを実装しました。すべてのクエリは、ここでトランザクションコンテキスト内で実行されています。
//Get Random account
1. select * from gas_accounts where is_locked = false order by random() limit 1
//Lock that account using the primary key
2. update gas_accounts set is_locked = true, last_updated_at = current_timestamp where public_key = :publicKey
//unlock the account
3. update gas_accounts set is_locked = false, last_updated_at = current_timestamp where public_key = :publicKey
50の並列スレッドで実行している場合、上記の関数は、戻り値にいつか約50秒かかります。これを行うに任意のより良い方法はありますか?
あなたの最初の選択と最初のアップデートとの競合状態があります:
- 2つの異なるスレッドが同時にステップ1を実行し、両方が同じ行にアクセスするために起こる想像します。その時点で
is_locked = false
、両方が同じ行を選択することが可能ですので。 - ステップ2において、両方のスレッドは、その行を更新しようとします。スレッド1はスレッド1のトランザクションがコミットされるまで、スレッド2はブロックされます、すぐに成功します。
- スレッド1つのコミット後、スレッド2が再び設定されます
is_locked = true
、同じ行にして、再度同じアカウントを処理します。 - これは望ましいことではない、あなたは待たなければならなかったのスレッドを持っているだけでなく、あなたが二度同じアカウントを処理しました。
代わりに、Postgresはと呼ばれる条項があるFOR UPDATE SKIP LOCKED
(ステップ2と3は変わらない)あなたはこのようにステップ1を行うことができるようになります:
SELECT * FROM gas_accounts WHERE is_locked = false LIMIT 1 FOR UPDATE SKIP LOCKED
通知は、あなたも必要ありませんorder by random()
(あなたのいずれかによってロックされていない使用可能な次の行を選択します。このようにis_locked
あなたはそれがランダム行だ検討することができるので、Postgresの自身のロック列もでは)。
何この句がやっていることは、レースの条件が完全に消えるので、各トランザクションには現在、他のトランザクションによってロックされていない1行を与えることです。ちょうどあなたがSELECTと同じトランザクション内の最初のUPDATEを実行していることを確認します。
追加の注記:
また、3文は同じトランザクションで起こる場合、あなたも持っている必要はありませんという通知is_locked
コラムを。ただ、使用してFOR UPDATE SKIP LOCKED
行をロックしてもその句を使用する他のトランザクションには見えないままになります。