テーブルが2つあります。 1つはログテーブルです。もう1つには、基本的に1回しか使用できないクーポンコードが含まれています。
ユーザーはクーポンを利用できる必要があります。これにより、ログテーブルに行が挿入され、クーポンが使用済みとしてマークされます(used
列をtrue
に更新することにより)。
当然、ここには明らかな競合状態/セキュリティ問題があります。
私は過去にもmySQLの世界で同様のことをしたことがあります。その世界では、両方のテーブルをグローバルにロックし、これは一度に1回だけ発生する可能性があることを認識してロジックを安全に実行し、完了したらテーブルのロックを解除します。
Postgresでこれを行うより良い方法はありますか?特に、ロックがグローバルであることに懸念がありますが、グローバルである必要はありません。他のユーザーがその特定のコードを入力しようとしていないことを確認するだけでよいので、行レベルのロックが機能する可能性があります。
MySQLのような同時実行性の問題については以前に聞いたことがあります。 Postgresではそうではありません。
デフォルトの組み込み行レベルロック READ COMMITTED
トランザクション分離レベル で十分です。
データを変更するCTE(MySQLにもないもの)を1つのステートメントで提案することをお勧めします。これは、(必要な場合に)あるテーブルから別のテーブルに直接値を渡すのが便利だからです。 coupon
テーブルの内容が必要ない場合は、UPDATE
および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;
複数のトランザクションが同じクーポンを引き換えようとするのはまれなものでなければなりません。彼らにはユニークな番号がありますね。同時に複数のトランザクションが同時に試行されることは、はるかにまれです。 (たぶん、アプリケーションのバグか、システムをゲームしようとしている誰か?)
とはいえ、UPDATE
は1つだけトランザクションに対してのみ成功し、何があっても。 UPDATE
は、更新前に各ターゲット行で行レベルのロックを取得します。並行トランザクションが同じ行をUPDATE
しようとすると、行のロックが表示され、ブロッキングトランザクションが完了するまで待機します(ROLLBACK
またはCOMMIT
)。ロックキューの最初:
コミットした場合は、状態を再確認してください。それでもNOT used
の場合は、行をロックして続行します。そうでない場合、UPDATE
は対象となる行を検出せず、nothingを実行して行を返しませんなので、INSERT
も何もしません。
ロールバックした場合は、行をロックして続行します。
競合状態の可能性はありません。
同じトランザクションに書き込みを追加するか、1つのトランザクションよりも多くの行をロックしない限り、 デッドロックの可能性はありません。 。
INSERT
は気にしないでください。誤ってcoupon_id
が既にlog
テーブルにある場合(そしてlog.coupon_id
にUNIQUEまたはPK制約がある場合)、一意の違反の後にトランザクション全体がロールバックされます。 DBでの不正な状態を示します。上記のステートメントがlog
テーブルに書き込む唯一の方法である場合、それが発生することはありません。