「労働者が自分の仕事をうまくやりたいなら、まず自分の道具を研ぎ澄まさなければなりません。」 - 孔子、「論語。陸霊公」
表紙 > プログラミング > Java と PostgreSQL を使用して競合状態に対処する方法

Java と PostgreSQL を使用して競合状態に対処する方法

2024 年 8 月 1 日に公開
ブラウズ:601

How to deal with race conditions using Java and PostgreSQL

ロックを使用してデータベースの同時実行性を制御する

あなたは電子商取引システムに取り組んでおり、何千人もの人々が最後に残った製品を同時に購入しようとしていると想像してください。ただし、多くの人はチェックアウトに進み、注文を完了することができました。在庫を確認すると、マイナスの数量の商品があります。これはどのようにして可能でしょうか?また、どうすれば解決できますか?

コーディングしてみよう!まず最初に考えられるのは、チェックアウト前に在庫を確認することです。おそらく次のようなものでしょう:

public void validateAndDecreaseSolution(long productId, int quantity {
    Optional stockByProductId = 
 stockRepository.findStockByProductId(productId);

    int stock = stockByProductId.orElseThrow().getStock();
    int possibleStock = stock - quantity;

    if (stock 



この検証を使用することはできますが、1 秒あたり数百、数千、数百万、さらには数十のリクエストについて話す場合、この検証では十分ではありません。 10 個のリクエストが同時にこのコードに到達し、データベースがstockByProductId に同じ値を返すと、コードは壊れます。この検証を行っている間、他のリクエストをブロックする方法が必要です。

最初の解決策 - 更新用

SELECT にロック ステートメントを追加します。この例では、Spring Data で FOR UPDATE を使用してこれを実行しました。 PostgreSQL のドキュメントにあるように

FOR UPDATE を使用すると、SELECT ステートメントによって取得された行が更新用のようにロックされます。これにより、現在のトランザクションが終了するまで、他のトランザクションによって変更または削除されることが防止されます。

@Query(value = "SELECT * FROM stocks s WHERE s.product_id = ?1 FOR UPDATE", nativeQuery = true)
Optional findStockByProductIdWithLock(Long productId);
public void validateAndDecreaseSolution1(long productId, int quantity) {
    Optional stockByProductId = stockRepository.findStockByProductIdWithLock(productId);

    // ... validate

    stockRepository.decreaseStock(productId, quantity);
}

製品 ID を使用した株式テーブルへのすべてのリクエストは、実際のトランザクションが終了するまで待機します。ここでの目的は、株価の最新の更新値を確実に取得することです。

2 番目の解決策 - pg_advisory_xact_lock

この解決策は前の解決策と似ていますが、ロック キーを選択できます。検証と在庫減少のすべての処理が完了するまで、トランザクション全体をロックします。

public void acquireLockAndDecreaseSolution2(long productId, int quantity) {
    Query nativeQuery = entityManager.createNativeQuery("select pg_advisory_xact_lock(:lockId)");
    nativeQuery.setParameter("lockId", productId);
    nativeQuery.getSingleResult();

    Optional stockByProductId = stockRepository.findStockByProductId(productId);

    // check stock and throws exception if it is necessary

    stockRepository.decreaseStock(productId, quantity);
}

このトランザクションが終了した後、次のリクエストは同じ ID を持つ商品とのみやり取りします。

3 番目の解決策 - WHERE 句

この場合、行またはトランザクションはロックされません。このトランザクションが更新ステートメントまで継続することを許可しましょう。最後の条件: 在庫 > 0 に注目してください。これにより、在庫がゼロ未満になることは許可されません。したがって、2 人が同時に購入しようとすると、データベースでは在庫が -1 未満であることが許可されないため、そのうちの 1 人がエラーを受け取ります。

@Transactional
@Modifying
@Query(nativeQuery = true, value = "UPDATE stocks SET stock = stock - :quantity WHERE product_id = :productId AND stock > 0")
int decreaseStockWhereQuantityGreaterThanZero(@Param("productId") Long productId, @Param("quantity") int quantity);

結論

最初と 2 番目の解決策は、戦略として悲観的ロックを使用します。 3 つ目は楽観的ロックです。悲観的ロック戦略は、リソースに関係するタスクを実行する際に、リソースへのアクセスを制限する場合に使用されます。プロセスが完了するまで、ターゲット リソースは他のアクセスに対してロックされます。デッドロックに注意してください!

オプティミスティックロックを使用すると、ブロックなしで同じリソースに対してさまざまなクエリを実行できます。競合が発生する可能性が低い場合に使用されます。通常、行に関連するバージョンがあり、この行を更新すると、データベースは行のバージョンとデータベース内の行のバージョンを比較します。両方が等しい場合、変更は成功します。そうでない場合は、再試行する必要があります。ご覧のとおり、この記事ではバージョン行を使用していませんが、3 番目の解決策はリクエストをブロックせず、ストック > 0 条件を使用して同時実行を制御します。

完全なコードを見たい場合は、私の GitHub で確認できます。

悲観的ロックと楽観的ロックを実装するための戦略は他にもたくさんあります。たとえば、FOR UPDATE WITH SKIP LOCKED について詳しく検索できます。

リリースステートメント この記事は次の場所に転載されています: https://dev.to/ramoncunha/how-to-deal-with-race-conditions-using-java-and-postgresql-4jk6?1 侵害がある場合は、study_golang@163 までご連絡ください。 .comを削除してください
最新のチュートリアル もっと>

免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。

Copyright© 2022 湘ICP备2022001581号-3