sql >> データベース >  >> RDS >> PostgreSQL

postgresql-トランザクションブロックを使用するスクリプトはすべてのレコードの作成に失敗します

    はい、あなたは何か間違ったことをしています。
    簡単な例を見てください。

    セッション1

    postgres=# select * from user_reservation_table;
     id | usedyesno | userid | uservalue
    ----+-----------+--------+-----------
      1 | f         |      0 |         1
      2 | f         |      0 |         2
      3 | f         |      0 |         3
      4 | f         |      0 |         4
      5 | f         |      0 |         5
    (5 wierszy)
    
    
    postgres=# \set user 1
    postgres=#
    postgres=# begin;
    BEGIN
    postgres=# UPDATE user_reservation_table
    postgres-# SET UsedYesNo = true, userid=:user
    postgres-# WHERE uservalue IN(
    postgres(#    SELECT uservalue FROM user_reservation_table
    postgres(#    WHERE UsedYesNo=false Order By id ASC Limit 1)
    postgres-# RETURNING uservalue;
     uservalue
    -----------
             1
    (1 wiersz)
    
    
    UPDATE 1
    postgres=#
    


    セッション2 -同時に、ただしわずか10ミリ秒後

    postgres=# \set user 2
    postgres=# begin;
    BEGIN
    postgres=# UPDATE user_reservation_table
    postgres-# SET UsedYesNo = true, userid=:user
    postgres-# WHERE uservalue IN(
    postgres(#    SELECT uservalue FROM user_reservation_table
    postgres(#    WHERE UsedYesNo=false Order By id ASC Limit 1)
    postgres-# RETURNING uservalue;
    

    セッション2がハングし、.......何かを待っています....

    セッション1に戻ります

    postgres=# commit;
    COMMIT
    postgres=#
    



    そして再びセッション2

     uservalue
    -----------
             1
    (1 wiersz)
    
    
    UPDATE 1
    postgres=# commit;
    COMMIT
    postgres=#
    

    セッション2はもう待機しておらず、トランザクションを終了します。

    そして、最終的な結果は何ですか?:

    postgres=# select * from user_reservation_table order by id;
     id | usedyesno | userid | uservalue
    ----+-----------+--------+-----------
      1 | t         |      2 |         1
      2 | f         |      0 |         2
      3 | f         |      0 |         3
      4 | f         |      0 |         4
      5 | f         |      0 |         5
    (5 wierszy)
    

    2人のユーザーが同じ値1を取りましたが、テーブルにはユーザー2のみが登録されています





    ======================EDIT ==================================

    このシナリオでは、SELECT .. FOR UPDATEを使用して、postgreがRead Committed IsolationLevelモードでクエリを再評価する方法を利用できます。
    ドキュメントを参照してください: http://www.postgresql.org/docs/9.2/static/transaction-iso.html

    つまり、
    一方のセッションが行をロックし、もう一方のセッションが同じ行をロックしようとすると、2番目のセッションは「ハング」し、最初のセッションがコミットまたはロールバックするのを待ちます。最初のセッションの場合トランザクションをコミットすると、2番目のセッションはWHERE検索条件を再評価します。検索条件が一致しない場合(最初のトランザクションが一部の列を変更したため)、2番目のセッションはその行をスキップし、WHEREに一致する次の行を処理します。条件。

    注:この動作は、繰り返し可能な読み取り分離レベルでは異なります。その場合、2番目のセッションでエラーがスローされます。同時更新のためにアクセスをシリアル化できなかったため、トランザクション全体を再試行する必要があります。

    クエリが次のようになります:

    select id from user_reservation_table
    where usedyesno = false
    order by id
    limit 1
    for update ;
    

    そして:

      Update .... where id = (id returned by SELECT ... FOR UPDATE)
    



    個人的には、プレーンな古いコンソールクライアント(postgreeの場合はpsql、oracleの場合はmysqlまたはSQLPlus)を使用してロックシナリオをテストすることを好みます

    では、psqlでクエリをテストしましょう:

    session1 #select * from user_reservation_table order by id;
     id | usedyesno | userid | uservalue
    ----+-----------+--------+-----------
      1 | t         |      2 |         1
      2 | f         |      0 |         2
      3 | f         |      0 |         3
      4 | f         |      0 |         4
      5 | f         |      0 |         5
    (5 wierszy)
    
    
    session1 #begin;
    BEGIN
    session1 #select id from user_reservation_table
    postgres-# where usedyesno = false
    postgres-# order by id
    postgres-# limit 1
    postgres-# for update ;
     id
    ----
      2
    (1 wiersz)
    
    
    session1 #update user_reservation_table set usedyesno = true
    postgres-# where id = 2;
    UPDATE 1
    session1 #
    

    セッション1がロックされ、行id=2が更新されました

    そして今度はセッション2

    session2 #begin;
    BEGIN
    session2 #select id from user_reservation_table
    postgres-# where usedyesno = false
    postgres-# order by id
    postgres-# limit 1
    postgres-# for update ;
    

    行ID=2をロックしようとしているときにセッション2がハングします

    OK、セッション1をコミットします

    session1 #commit;
    COMMIT
    session1 #
    

    セッション2で何が起こるか見てみましょう:

    postgres-# for update ;
     id
    ----
      3
    (1 wiersz)
    

    ビンゴ-セッション2は行ID=2をスキップし、行ID =3を選択(およびロック)しました


    したがって、最終的なクエリは次のようになります。

    update user_reservation_table
    set usedyesno = true
    where id = (
       select id from user_reservation_table
       where usedyesno = false
       order by id
       limit 1
       for update
    ) RETURNING uservalue;
    

    いくつかの予約-この例はテスト目的のみであり、postgreでロックがどのように機能しているかを理解するのに役立ちます。
    実際、このクエリはテーブルへのアクセスをシリアル化し、スケーラブルではなく、おそらく実行されますマルチユーザー環境では悪い(遅い)。
    10セッションが同時にこのテーブルから次の行を取得しようとしていると想像してください。各セッションはハングし、前のセッションがコミットされるまで待機します。
    したがって、使用しないでください。プロダクションコードでのこのクエリ。
    本当に「テーブルから次の値を見つけて予約」しますか?なぜですか?
    はいの場合、シリアル化デバイスが必要です(このクエリのように、または実装が簡単な場合は、テーブル全体をロックする必要があります)が、これがボトルネックになります。




    1. パフォーマンス:rank()とサブクエリ。サブクエリのコストは低くなりますか?

    2. mysql jarをlibに追加しましたが、intelliJを介して動作するtomcatへのローカルデプロイメントでクラスに直面している例外が見つかりませんでしたか?

    3. PostgreSQLで類似した文字列をすばやく見つける

    4. MSAccessチームのスペシャルゲストミハルバーと一緒に参加しましょう!