MySQL同時発生による汚れたデータ分析

4430 ワード

同時に発生した汚れたデータの問題(MySQLベース)を記録します.問題の説明(銀行のオペレーターの例):例えばA、Bオペレーターは同時に1つの残高が1000元の口座を読み取って、Aオペレーターはこの口座のために100元増加して、Bオペレーターは同時にこの口座のために50元差し引いて、Aは先に提出して、Bは後で提出します.最後の実際の口座残高は1000-50=950元ですが、本来は1000+100-50=1050です.
まず、問題がどのように発生するかを分析します.
            A          :
	
	User user = userDao.getById(1);      ----------(  1)
	user.setAccount(user.getAccount()+100);    ----------(  2)
	userDao.update(user);         ------------------(  3)

 B         :
	User user = userDao.getById(1);      ----------(  1)
	user.setAccount(user.getAccount()-50);    ----------(  2)
	userDao.update(user);         ------------------(  3)

A操作のプログラムがステップ1の後、ステップ3の前に進むと、Bはステップ1を実行する.その後、Aはステップ3を実行し、Bはステップ2と3を実行する.この時点で問題が発生します.これが典型的な同時発生による汚いデータの問題である.コードにトランザクションを付けようという人もいますが、方法に@Transationalを入れるとこの問題は発生しませんか.ここで私はこのようにするのは役に立たないと言いたい.もちろん、トランザクションを追加してもこの問題を解決できますが、トランザクションの独立性レベルをSERIALIZABLE(シリアル化可能)に設定する必要があります.しかし、これは、同時実行の場合、特に効率が低下します(データ行に対する要求ごとに、読み取りと書き込みを含む要求は、前の読み取りと書き込みがすべて実行されるのを待つ必要があります).ただし、MySQLのデフォルトのトランザクション独立性レベルはREPEATABLE(繰り返し読み可能)であり、この独立性レベルでは、Aがステップ1を実行した後にデータ行にリードロックをかけたとしても、Bがステップ1を実行したときにデータ行を読み取ることができ、このときに幻読みの問題が発生する.だから単純に方法に@Transational注記を入れるだけでは問題は解決できません.
関連知識点:
トランザクションの4つの特性:原子性、一貫性、隔離性、持続性
  • アトミック:トランザクションに含まれるすべての操作が、すべて実行されるか、すべて実行に失敗します.
  • 一貫性:トランザクションに含まれるすべてのオペレーション設計のデータ行は、すべての実行が完了した結果、またはすべての完了前の結果を表示できます.つまり、明ちゃんは100元、花は10元、明ちゃんは50元回転しました.では、他の事務にとって、見えるのは明ちゃんが50元で、小さな花が60元あることだけです.あるいは明ちゃんは100元で、小さな花は10元です.明ちゃんは50元で、花は10元ではありません.これをコンシステンシ
  • と呼ぶ
  • 永続性:トランザクションの実行が完了すると、データはディスクに永続化されます.
  • 独立性:独立性には4つの独立性レベルがあります:
  • ①Serializable(シリアル化):汚れ読み、繰り返し読み、幻読みの発生を避けることができます.
    ②Repeatable read(繰り返し読み可能):汚読み、再読み不可の発生を避けることができる.
    ③Read committed(既読提出):汚読の発生を避けることができる.
    ④Read uncommitted(リードコミットなし):最低レベルで、いかなる状況でも保証できない.
    では、汚い読み、幻読みとは何でしょうか.
    1、汚い読み
    ダーティ・リードとは、あるトランザクション・プロシージャで別のコミットされていないトランザクションのデータが読み込まれたことを意味します.
    1つのトランザクションがデータを複数回変更しているのに、このトランザクションで複数回の変更がコミットされていない場合、1つの同時トランザクションがデータにアクセスすると、2つのトランザクションで得られたデータが一致しません.例えば、ユーザーAはユーザーBに100元振り込み、SQLコマンドは以下の通りである.
        update user set account=account+100 where name=’B’;  (  A  B)
        update user set account=account - 100 where name=’A’;
    

    1番目のSQLのみを実行する場合、AはBに口座の表示を通知し、Bは確実にお金が入金されたことを発見し(このとき汚れた読み取りが発生した)、その後、2番目のSQLが実行されるかどうかにかかわらず、その事務が提出されない限り、すべての操作がロールバックされ、Bが後で再び口座を表示すると、お金が実際に回転していないことがわかります.
    2、繰り返し不可
    再読み取り不可とは、データベース内のデータに対して、1つのトランザクション範囲内で複数のクエリが異なるデータ値を返したことを意味します.これは、クエリ間隔で別のトランザクションによって変更され、コミットされたためです.
    例えば、トランザクションT 1はあるデータを読み出しているが、トランザクションT 2はすぐにそのデータを修正してデータベースにコミットし、トランザクションT 1は再びそのデータを読み出して異なる結果を得て、繰り返し不可読み出しを送信する.
    重複しない読み取りと汚い読み取りの違いは、あるトランザクションが別のトランザクションでコミットされていない汚いデータを読み取り、重複しない読み取りは前のトランザクションでコミットされたデータを読み取ることです.
    場合によっては、繰り返し不可は問題ではありません.たとえば、データを複数回クエリーするのは、もちろん最後のクエリーで得られた結果が主です.しかし、別の場合、同じデータAとBに対して順番にクエリを行うと異なる場合があり、AとBがヒットする可能性があるなどの問題が発生する可能性があります.
    3,虚読み(幻読み)
    幻読は、トランザクションが独立して実行されない場合に発生する現象です.たとえば、トランザクションT 1は、1つのテーブル内のすべてのローのデータ・アイテムを「1」から「2」に変更する操作を行い、トランザクションT 2はまた、このテーブルにデータ・アイテムのローを挿入し、そのデータ・アイテムの数値は「1」であり、データベースにコミットします.一方、トランザクションT 1を操作するユーザは、変更したばかりのデータを再表示すると、変更されていない行があることに気づきます.実際には、トランザクションT 2から追加され、幻覚を起こすように幻読が発生します.
    幻読と重複不可読は、もう1つのコミットされたトランザクション(この点ではダーティリードが異なります)を読み取り、重複不可クエリーは同じデータ項目であり、幻読はデータ全体(データの個数など)を対象としています.
     
    バラバラが山積みになっているので、今から解決策についてお話しします(自分の考え、足りないところは指摘してください~)
    この問題に対して楽観的なロックを使用して実現することができます.userテーブルにversionフィールドを追加し、updateを実行するたびにversion++を実行します.
       A   sql  :
    select id, account,version from user where id="1"; 
        :id=1, account=1000, version=1
     
    update user
    setaccount=account+100, version=version+1 
    whereid="1" and version=1
     
    select id, account,version from user where id="1"; 
        :id=1, account=1100, version=2
    
       B   sql  
    select id, account, version from user where id="1"; 
        :id=1, account=1000, version=1
    #   A     ,  user.account=1100、user.version=2,   B       (version=2)          (account=950),             
      ,   B         2,           2,    “                     “      ,  ,   B      。
    update userset account=account-50, version=version+1 where id="1" and version=1
    select id, account, version from user where id="1"; 
        :id=1, account=1100, version=2