MySQLデッドロックについてのあなたの理解について話してください

1.デッドロックとは何ですか?

デッドロックとは、2つ以上の異なるプロセスまたはスレッドで、共通のリソースの競合またはプロセス(またはスレッド)間の通信のために、各スレッドが一時停止して相互に待機するという事実を指します。外力がない場合、最終的にはシステム全体がクラッシュします。

    2。Mysqlデッドロックに必要な条件

  • リソース独占条件

同じリソースをめぐって競合する複数のトランザクションの相互排除を指します。つまり、リソースは一定期間1つのトランザクションによってのみ占有されます。これは、排他的リソース(行ロックなど)とも呼ばれます。

  • 条件の要求と保留

これは、トランザクションaでロックAが取得されたが、新しいロックB要求が行われ、ロックBがすでに他のトランザクションbによって占有されていることを意味します。このとき、トランザクションaはブロックされますが、ロックAは取得したものはブロックされます。

  • 剥奪条件なし

これは、ロックAがトランザクションaで取得されており、コミットされる前に奪うことができないことを意味します。ロックAは、トランザクションが使用されてからそれ自体で解放された後にのみコミットできます。

  1. 相互取得ロック状態

つまり、デッドロックが発生した場合、相互にロックを取得するプロセスが必要です。つまり、トランザクション保持ロックAがロックBを取得する一方で、トランザクションb保持ロックBもロックAを取得し、最終的に相互取得につながります。各トランザクションはブロックされます。

    3。Mysqlクラシックデッドロックケース

アカウントAがアカウントBに50元を転送し、アカウントBもアカウントAに30元を転送する転送シナリオがあるとすると、このプロセスでデッドロック状態が発生しますか?

    3.1テーブルステートメントの作成

CREATE TABLE `account`(
  ` id` int(11)NOT NULL COMMENT'主キー'、
  `user_id` varchar(56)NOT NULL COMMENT'ユーザーID'、
  ` balance` float(10,2)DEFAULT NULL COMMENT'balance '、
  PRIMARY KEY( `id`)、
  UNIQUE KEY` idx_user_id`( `user_id`)USING BTREE 
)ENGINE = InnoDB DEFAULT CHARSET = utf8 COMMENT='アカウント残高テーブル';

    3.2関連データの初期化

INSERT INTO `test`.`account`(` id`、 `user_id`、` balance`)VALUES(1、'A'、80.00); 
INSERT INTO `test`.`account`(` id`、 `user_id`、` balance`)VALUES(2、'B'、60.00);

    3.3通常の転送プロセス

デッドロックの問題について話す前に、通常の転送プロセスを見てみましょう。正常

この場合、ユーザーAはユーザーBに50元を送金しますが、これは1回のトランザクションで完了できます。ユーザーAの残高とユーザーBの残高を最初に取得する必要があります。これら2つのデータは後で変更する必要があるため、後で変更する必要があります。書き込みロックを渡すために必要です(UPDATEの場合)他のトランザクションの変更によって変更が失われることによって引き起こされるダーティデータを防ぐために、それらをロックします。

関連するSQLは次のとおりです。

トランザクションを開始する前に、mysqlの自動コミットをオフにする必要があります

set autocommit = 0; 
#トランザクションの自動コミットステータスを表示する

show VARIABLES like 'autocommit';![在这里插入图片描述](https://img-blog.csdnimg.cn/a486a4ed5c9d4240bd115ac7b3ce5a39.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_

Q1NETiBA6ZqQIOmjjg==,size_20,color_FFFFFF,t_70,g_se,x_16)

#転送sql 
START TRANSACTION; 
#Aの残高を取得し、A_balance変数に保存し
ます。80SELECT user_id、@ A_balance:= UPDATEの場合、user_id ='A'であるアカウントからの残高;#B
の残高を取得し、 B_balance変数:60 
SELECT user_id、@ B_balance:= balance from account where user_id ='B' for UPDATE; #A

の残高を変更する
UPDATE account set balance = @A_balance --50 where user_id ='A'; 
#Bの残高を変更する
UPDATE account set balance = @ B_balance + 50 where user_id ='B'; 
COMMIT;

実行後の結果:

データの更新は正常であることがわかります

    3.4デッドロック転送プロセス

初期化されたバランスは次のとおりです。

このシナリオが高い同時実行性の下で存在するとします。ユーザーAがユーザーBに50元を転送すると、ユーザーBもユーザーAに30元を転送します。

次に、Javaプログラム操作のプロセスとタイムラインは次のとおりです。

  1. ユーザーAはユーザーBに50元を送金し、SQLを実行するためにプログラムでトランザクション1を開き、Aの残高を取得してAのデータをロックする必要があります。

#トランザクション1 
set autocommit = 0; 
START TRANSACTION; 
#Aの残高を取得し、それをA_balance変数に格納し
ます。80SELECT user_id、@ A_balance:= account from account where user_id ='A' for UPDATE;

2.ユーザーBがユーザーAに30元を送金する場合、SQLを実行し、Bの残高を取得し、Bのデータをロックするために、プログラムでトランザクション2を開く必要があります。

#トランザクション2 
set autocommit = 0; 
START TRANSACTION; 
#Aの残高を取得し、それをA_balance変数に格納し
ます。60SELECT user_id、@ A_balance:= account from account where user_id ='B' for UPDATE;

3.トランザクション1で残りのSQLを実行します

#Bの残高を取得し、B_balance変数に保存し
ます。60SELECT user_id、@ B_balance:= UPDATEの場合はuser_id='B'のアカウントからの残高; #A

の残高を変更する
UPDATE account set balance = @A_balance --50 where user_id ='A '; #B
の残高を変更する
UPDATEアカウントセットbalance = @B_balance + 50 where user_id ='B'; 
COMMIT;

トランザクション1でBデータの書き込みロックを取得したときにタイムアウトが発生したことがわかります。なんでそうなの?主に、ステップ2でトランザクション2でBデータの書き込みロックを取得したため、トランザクション2がコミットまたはロールバックする前にトランザクション1がBデータの書き込みロックを取得することはありません。

4.トランザクション2で残りのSQLを実行します

#Aの残高を取得し、B_balance変数に保存し
ます。60SELECT user_id、@ B_balance:= UPDATEの場合はuser_id='A'のアカウントからの残高; #B

の残高を変更する
UPDATE account set balance = @A_balance-30 where user_id = 'B'; 
#Aの残高を変更する
UPDATEアカウントセットbalance = @B_balance + 30 where user_id ='A'; 
COMMIT;

同様に、トランザクション2でAデータの書き込みロックが取得された場合にもタイムアウトが発生します。ステップ1のトランザクション1でデータAの書き込みロックが取得されているため、トランザクション1がコミットまたはロールバックする前に、トランザクション2がデータAの書き込みロックを取得することはありません。

5.なぜこれが起こるのですか?

主な理由は、トランザクション1とトランザクション2が互いにロックを取得するのを待機するプロセスを持っているため、両方のトランザクションがハングしてブロックされ、最終的にロック取得タイムアウトの例外がスローされます。

    3.5デッドロックによる問題

ご存知のように、データベースの接続リソースは非常に貴重です。トランザクションのブロックにより接続が長期間解放されない場合、新しいリクエストによって実行されるSQLもキューに入れられて待機し、蓄積が行われます。最終的にアプリケーション全体を下にドラッグします。アプリケーションをヒューズ処理なしでマイクロサービスシステムにデプロイすると、リンク全体がブロックされるため、アバランシェ効果が発生し、重大な製造事故が発生します。

    4。デッドロックの問題を解決するにはどうすればよいですか?

デッドロックの問題を解決するために、デッドロックの4つの必要な条件から始めることができます

入手。

なぜなら

リソース排他条件と非剥奪条件は、ロックの本質の機能的表現であり、変更できないため、他の2つの条件から解決しようとします。

    4.1要求と保留の条件を破る

上記の定義によれば、これはトランザクション1とトランザクション2が同時にロックAとロックBを競合するために発生します。したがって、ロックAとロックBは、一度に1つのトランザクションのみが競合および保持できることを保証できますか?
答えはイエスです。以下の擬似コードを見てみましょう。

/ ** 
*トランザクション1の入力パラメーター(A、B)
*トランザクション2の入力パラメーター(B、A)
** / 
public void transferAccounts(String userFrom、String userTo){ 
     //分散ロックを取得
     ロックロック= Redisson.getLock() ; 
     //トランザクションを開始します
     JDBC.execute( "START TRANSACTION;"); 
     //転送を実行します
     sqlJDBC.execute( "#Aのバランスを取得し、A_balance変数に格納します:80 \ n" + 
             "SELECT user_id 、@ A_balance:= account from account where user_id ='"+ userFrom +"' for UPDATE; \ n "+ 
             "#Bの残高を取得し、B_balance変数に格納します:60 \ n "+ 
             " SELECT user_id、@ B_balance:=アカウントからのバランスwhere user_id ='"+ userTo +"' for UPDATE; \ n "+ 
             " \ n "+ 
             "#Aのバランスを変更する\ n "+
             "UPDATE account set balance = @A_balance --50 where user_id ='" + userFrom + "'; \ n" + 
             "#Bの残高を変更\ n" + 
             "UPDATE account set balance = @B_balance + 50 where user_id ='" + userTo + "'; \ n"); 
     //トランザクションをコミットします
     JDBC.execute( "COMMIT;"); 
     //ロックを解除します
     lock.unLock(); 
}

上記の擬似コードは、すべてのトランザクションが分散ロックを介してシリアルに実行されるため、明らかにデッドロックの問題を解決できます。

それで、それは本当に大丈夫ですか?

トラフィックが少ない場合は問題ないようですが、同時実行性の高いシナリオでは、より多くのマシンをデプロイしても、分散ロックのためにビジネスが制限されるため、これがサービス全体のパフォーマンスのボトルネックになります。直列に実行され、クラスターの展開によってサービスのパフォーマンスが同時実行性を向上させることはありません。高速で正確かつ安定した分散サービスの要件を満たすことができないため、デッドロックの問題を別の方法で解決する方法を見てみましょう。

    4.2相互取得ロック状態を解除する(推奨)

この条件を破るのは実際には非常に簡単です。つまり、ロックを取得するプロセスで、トランザクションは順次取得を保証できます。つまり、ロックAは常にロックBの前に取得されます。
以前の擬似コードを最適化する方法を見てみましょう。

/ ** 
*トランザクション1の入力パラメーター(A、B)
*トランザクション2の入力パラメーター(B、A)
** / 
public void transferAccounts(String userFrom、String userTo){ 
     //userFromが常にForになるようにユーザーAとBを並べ替えますユーザーA、userToは常にユーザーB
     ですif(userFrom.hashCode()> userTo.hashCode()){ 
         String tmp = userFrom; 
         userFrom = userTo; 
         userTo = tmp; 
     } 
     //トランザクションを開始します
     JDBC.execute( "START TRANSACTION;" ); 
     // transfer sql 
     JDBC.execute( "#Aのバランスを取得し、それをA_balance変数に格納します:80 \ n" + 
             "SELECT user_id、@ A_balance:= balance from account where user_id ='" + userFrom + "'for UPDATE; \ n" + 
             "#Bのバランスを取得し、B_balance変数に格納します:60 \ n" +
             "SELECT user_id、@ B_balance:= account from account where user_id ='" + userTo + "'for UPDATE; \ n" +
             "\ n" + 
             "#Aの残高を変更\ n"+ 
             "UPDATEアカウントセットの残高=@A_balance-50where user_id ='" + userFrom + "'; \ n" + 
             "#Bの残高を変更\ n" + 
             "UPDATE account set balance = @B_balance + 50 where user_id ='"+ userTo +"'; \ n "); 
     //トランザクションをコミットします
     JDBC.execute(" COMMIT; "); 
 }

トランザクション1の入力パラメーターが(A、B)であり、トランザクション2の入力パラメーターが(B、A)であると想定します。2つのユーザーパラメーターをソートしたので、トランザクション1では、最初にロックAを取得する必要があります。次にロックBを取得します。同じことがトランザクション2にも当てはまります。トランザクション2では、最初にロックAを取得してから、ロックBを取得する必要があります。両方のトランザクションが順番にロックを取得するため、相互にロックを取得する条件が破られ、最終的にデッドロックの問題が発生します。完全に解決しました。

    5。まとめ

mysqlはインターネットで広く使用されているため、デッドロックの問題がよく聞かれます。兄弟がこの知識を習得し、競争力を向上できることを願っています。

ソース:

https://www.cnblogs.com/yin-feng/p/16041014.html

乾物共有

最近、個人の学習ノートを1冊の本にまとめ、PDFを使用して共有しました。私に従ってください、次のコードに返信してください、あなたはBaiduディスクアドレスを得ることができます、受け取るルーチンはありません!

•001:「Java同時実行および高同時実行ソリューション」研究ノート;•002:「詳細なJVMカーネル-原理、診断および最適化」研究ノート;•003:「Javaインタビューコレクション」•004:「Dockerオープンソースブック」 •005:「Kubernetesオープンソースブック」•006:「DDDクラッシュ(ドメイン駆動設計クラッシュ)」

おすすめ

転載: blog.csdn.net/Trouvailless/article/details/124435851