PostgresでのUPDATE / INSERTの組み合わせのロック


11

テーブルが2つあります。1つはログテーブルです。もう1つには、基本的に1回しか使用できないクーポンコードが含まれています。

ユーザーはクーポンを利用できる必要があります。これにより、ログテーブルに行が挿入され、クーポンが使用済みとしてマークされます(used列をに更新することによりtrue)。

もちろん、ここには明らかな競合状態/セキュリティ問題があります。

私は過去にもmySQLの世界で同様のことをしたことがあります。その世界では、両方のテーブルをグローバルにロックし、これは一度に1回だけ発生する可能性があるという知識のもとでロジックを安全に実行し、完了したらテーブルのロックを解除します。

Postgresでこれを行うより良い方法はありますか?特に、ロックがグローバルであることを心配していますが、グローバルである必要はありません。他の誰かがその特定のコードを入力しようとしていないことを確認するだけでよいので、行レベルのロックが機能するでしょうか?

回答:


15

MySQLのような同時実行性の問題については以前に聞いたことがあります。Postgresではそうではありません。

デフォルトのREAD COMMITTEDトランザクション分離レベルの組み込み行レベルロックで十分です。

データを変更するCTE(MySQLにもないもの)を使用した単一のステートメントをお勧めします。あるテーブルから別のテーブルに値を直接渡すことが必要な場合(必要な場合)に便利だからです。couponテーブルから何も必要ない場合は、個別のUPDATEand INSERTステートメントを使用したトランザクションも使用できます。

WITH upd AS (
   UPDATE coupon
   SET    used = true
   WHERE  coupon_id = 123
   AND    NOT used
   RETURNING coupon_id, other_column
   )
INSERT INTO log (coupon_id, other_column)
SELECT coupon_id, other_column FROM upd;

それはする必要があります珍しい複数のトランザクションの試みは、同じクーポンを利用する事。彼らにはユニークな番号がありますね。同時に複数のトランザクションが同時に試行されることは、はるかにまれです。(たぶん、アプリケーションのバグか、システムをゲームしようとしている誰か?)

とにかく、何が起こっても、1つのトランザクションでUPDATEのみ成功します。は更新前に各ターゲット行の行レベルのロックを取得します。並行トランザクションが同じ行を試行すると、行のロックが表示され、ブロッキングトランザクションが終了するまで(または)待機してから、ロックキューの最初になります。UPDATEUPDATEROLLBACKCOMMIT

  • コミットした場合は、状態を再確認してください。まだの場合NOT usedは、行をロックして続行します。それ以外の場合、UPDATE現在、対象となる行が見つからず、何も実行されず、行返されないため、INSERTも何も実行しません。

  • ロールバックされた場合は、行をロックして続行します。

競合状態になる可能性はありません

同じトランザクションにより多くの書き込みを行うか、1つのトランザクションよりも多くの行をロックしない限りデッドロックの可能性はありません

INSERTケアフリーです。なんらかの間違いによりcoupon_idすでにlogテーブルに存在している(そしてUNIQUEまたはPK制約がにあるlog.coupon_id)場合、一意の違反の後にトランザクション全体がロールバックされます。DBでの不正な状態を示します。上記のステートメントがlogテーブルに書き込む唯一の方法である場合、それが発生することはありません。


実際、複数のトランザクションが同じコードを引き換えようとすることはまれなことですが、誰かがシステムをゲームしようとしているときに、これが排他的に発生するという疑いがあります。CTEは私にとってPostgresへの移行において大きな魅力でしたが、暗黙のロックがこれで十分であるとは思いませんでした。
Rob Miller
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.