JPAによるトランザクションの同期処理


ココナッツ種子(Concurrency)

導入する


週末の文章を読んだ.
https://lion-king.tistory.com/entry/SpringJPATransaction-Write-skew-Phantom-1
JPAを使用した取引で同期性の問題が発生しました.
一般的な更新ではありません.
select - isRowExist ? update:insertの場合.
実務の間には似たような構造の表があり、関連話題をよく読んだ.

コンテンツの理解


最初から聞き苦しい話があった.
「オブジェクトリレーションシップマッピングフレームワークを使用すると、データベースが提供する原子演算を意外に使用するのではなく、不安定な読み取り-修正-書き込みサイクルを実行するコードを簡単に作成できます.」
JPA(Hibiernate-ORM)を使用する場合は、いくつかのトランザクションを考慮する必要があります.
理由は以下の通り.
独立性レベル
通常、主に使用されるデータベースは、通常、READ COMMITTEDに対応する独立性レベルを有する.ただし、JPAを使用している場合は、セカンダリコンテキストにロードされたエンティティを再度問い合わせると、データベースを問い合わせるのではなく、セカンダリコンテキストからエンティティをインポートするため、REPEATABLE READ独立性レベルと同じ動作をします.
https://reiphiel.tistory.com/entry/understanding-jpa-lock
では、JPAの隔離レベルはどのように決まるのでしょうか.
JPA(Hibernate)の隔離はデータベースベンダーによって決まるそうです
また、Springの@Transactionalで変更することもできます.
ただし、上記の永続キャッシュにはいつでも注意が必要です.
*Hibernateのdefault独立性レベル
https://allaroundjava.com/transaction-management-hibernate/
1つの例は、ライトワープです.どのような値段を書いて、もととは違う内容を書いたのか、理解できます.よく見ると、以下のようになります.
dirty checkingメカニズムに従って、永続的なオブジェクトの値のみが更新されます.ただし、dirty checking更新時の目標値が変化した場合は、その部分が認識できないことを示します.残高を引き出し、ポイントを差し引く
(loss updateホットスポットと見なすことができ、2つの違いは以下の参照リンクで置き換えられます.)
A beginner’s guide to Read and Write Skew phenomena
https://vladmihalcea.com/a-beginners-guide-to-read-and-write-skew-phenomena/

ソリューション


Optimistic(楽観)Lock
トランザクション間でロックが発生しないという観点を楽観ロックと呼ぶ.
JPAが提供する@Versionを使用して、フォームにVersionコラムを明記し、更新中に該当するコラム+1を更新します.
トランザクション間のupdateオブジェクトのバージョンコラムデータが永続オブジェクトのバージョンデータと異なる場合は、Exceptionが発生した後にトランザクションを補償するなどの方法を選択します.
PESSIMISTIC(悲観)ロック
トランザクション間でロックが発生すると考えられるため、悲観ロックと呼ばれます.
mysqlは~~for updateのようにロックを要求する.
JPAロックについて
https://reiphiel.tistory.com/entry/understanding-jpa-lock

テストと結果


前述したように、実務にも似たような論理がある.
特定のIDを選択した後、関連テーブルに値がない場合、insertはupdateになります.
上のリンクを参照して、以下のテストコードを作成します.
	@Test
    void updateConcurrencyTest() throws InterruptedException
    {
		Long Id = 100L;
        int numberOfThreads = 10;
        ExecutorService service = Executors.newFixedThreadPool(10);
        CountDownLatch latch = new CountDownLatch(numberOfThreads);

        boolean result;
        for(int i = 0; i < numberOfThreads; i++) {
            int finalI = i;
            service.execute(() -> {
                try {
                    //테스트될 메소드
                    service.updateById(Id, UseYn.Y);
                    System.out.println("Tid : " + finalI);
                } catch(Exception e) {
                    e.printStackTrace();
                }
                latch.countDown();
            });
        }

        latch.await();

        Entity entity = entityRepository.findById(Id)
            .orElseThrow(() -> new RuntimeExeception("Not Found"));


        Assertions.assertEquals(UseYn.Y, entity.getUseYn());
    }

同じINSERT論理で同じエラーが発生しました
Insertポイントでは、ターゲットに一意の値が存在します.
試す
1.PESSIMISTIC(悲観的)ロック-PESISIMISTIC WRITE-成功
@Lock(LockModeType.PESSIMISTIC_WRITE)
List<Entity> findAllByEntityIdIn(@Param("Id") List<Long> Id);
(repositoryメソッドにプレゼンテーションを追加しました.)
select ..
where Id in (1234) for update
x lockをfor updateクエリーとして使用しました.
一度だけ挿入して、残りは更新しました.
例ではdead lockが掛かっている(なぜ...?)成功してよかった.
2.PESSIMISTIC(悲観)ロック-PESIMISTIC READ-失敗
select ..
where id in (1234) lock in share mode 
前述したように、クエリーはlock in shareモードで実行されます.
(注意:MySQL 8.0から、既存のLOCKIN SHARE MODEではなくFOR SHAREに簡略化できます(後方互換性のため、既存の構文も正常に動作します).
share lockを使用しているため、以下のDeadlockが発生しました.

3.Optimical(楽観的)ロック-OptimificLockExceptionおよび補償トランザクション-失敗
OptimisticLockExceptionは、トランザクションが失敗したターゲットエンティティを返すための便利な方法を提供する楽観的ロックを使用するトランザクションの例外です.

一番成功しやすいと思ったが失敗した.
OptimificLockExceptionが発生する前にDeadLockが発生し、トランザクションの失敗に対する補償トランザクションを実行できません.
なぜなら、私は上のduplicate keyに夢中になって、「存在しなければ挿入」論理に集中しているからです.根本的に言えば、rowの更新を理解するためにversionコラムを作成します.
これはinsertの場合ではなく、loss updateとしての防犯措置です.
Hibernate Optimistic Locking for Concurrent Insert-possible?
https://stackoverflow.com/questions/15761661/hibernate-optimistic-locking-for-concurrent-insert-possible
何が一番いいですか.
上記のテストを行う過程で,悲観的および楽観的なポイントロックを用いて解決策を探した.
Insertとupdateを1つの方法で行う特殊な場合には,insertとupdateの場合に応じて適切なロック技術を用いることも重要である.
各種のロック方法の特徴と長所と短所を正確に把握することも必要である.
同期化の問題-ビジネスアプリケーション
楽観的なロックテクノロジー(同期制御テクノロジーの1つ)を使用してアーキテクチャを構築することで、信頼性の低い在庫値の問題を解決できます.しかし,全過程にわたって「外部システム連動」などの過程が存在すると,楽観的ロックを用いることは困難である.楽観的なロックは、ロールバックが困難なプロセスであれば、システム全体の一貫性が破壊されるため、前回のストレージ試行時にプロセス全体の失敗を発見することができます.
この場合、システムのアクティビティを少し放棄しても、「悲観的ロック」を使用して精度を向上させることができます.
楽観的なロックに比べて、悲観的なロックのアクティビティは小さく、受注セットの場合、顧客はより遅い受注を体験する可能性がありますが、これにより、顧客体験が正しくないシステム(例えば、支払いと受注のキャンセルのみ)の確率が低下します.
http://jaynewho.com/post/44

その他のコメント


Database Transactions: Difference between 'write skew' and 'lost update'
https://stackoverflow.com/questions/27826714/database-transactions-difference-between-write-skew-and-lost-update
トランザクションの独立性レベル(Lockと理解)
https://suhwan.dev/2019/06/09/transaction-isolation-level-and-lock/
Mysql innoDBロック、独立性レベル、ロック競合
https://taes-k.github.io/2020/05/17/mysql-transaction-lock/
[JPA]持続性コンテキストとリフレッシュについて
https://ict-nroo.tistory.com/130
Transaction isolation in Hibernate
https://www.waitingforcode.com/hibernate/transaction-isolation-in-hibernate/read
Optimistic LockとPersimisic Lock
https://effectivesquid.tistory.com/entry/Optimistic-Lock%EA%B3%BC-Pessimistic-Lock