MySQLデッドロック問題の詳細な分析

MySQLデッドロック問題の詳細な分析

序文

私たちのビジネスがまだ初期段階にあり、同時実行の度合いが比較的低い場合、数年間はデッドロックの問題に遭遇しないかもしれません。逆に、私たちのビジネスの同時実行の度合いが非常に高い場合、時々発生するデッドロックの問題は間違いなく私たちを非常に困惑させるでしょう。しかし、デッドロック問題が発生すると、多くの経験の浅い学生の最初の反応はダチョウになることです。「これは非常に高度な問題で、理解できません。運命に任せましょう。いつも起こるわけではありません。」実際、MySQL のステートメント ロックの分析に関する以前に書いた 3 つの記事と、デッドロック ログの分析に関するこの記事を注意深く読めば、デッドロック問題の解決はそれほど混乱することはないはずです。

準備

ストーリーがスムーズに展開するためには、表を作成する必要があります。

テーブルの作成ヒーロー(
 id INT、
 名前 VARCHAR(100)、
 国varchar(100)、
 主キー (id)、
 KEY idx_name (名前)
) エンジン=InnoDB CHARSET=utf8;

hero テーブルの id 列にクラスター化インデックスを作成し、name 列にセカンダリ インデックスを作成しました。この英雄テーブルは主に三国時代の英雄を保存するために使用されます。テーブルにいくつかのレコードを挿入します。

ヒーロー値に挿入
 (1、「劉備」、「蜀」)、
 (3、「諸葛亮」、「蜀」)、
 (8、「曹操」、「魏」)、
 (15、「x荀玉」、「魏」)、
 (20、「孫権」、「呉」)

テーブル内のデータは次のようになります。

mysql> SELECT * FROM hero;
+----+------------+---------+
| ID | 名前 | 国 |
+----+------------+---------+
| 1 | l劉備| 蜀|
| 3 | 諸葛亮 | 蜀 |
| 8 | c曹操 | 魏 |
| 15 | xXun Yu | 魏 |
| 20 | s孫権 | 呉 |
+----+------------+---------+
セット内の行数は 5 です (0.00 秒)

準備は完了です。

デッドロックシナリオの作成

まず、デッドロック シナリオを作成し、セッション A とセッション B でそれぞれ 2 つのトランザクションを実行してみましょう。具体的な状況は次のとおりです。

分析してみましょう:

  • ステップ ③ からわかるように、セッション A のトランザクションは、まず、ヒーロー テーブルのクラスター化インデックス内の ID 値 1 のレコードに X タイプのポジティブ レコード ロックを追加します。
  • ステップ ④ からわかるように、セッション B のトランザクションは、ヒーロー テーブルのクラスター化インデックス内の ID 値 3 のレコードに X タイプのポジティブ レコード ロックを追加します。
  • ステップ 5 からわかるように、セッション A のトランザクションは、ヒーロー テーブルのクラスター化インデックス内の ID 値 3 のレコードに X タイプの通常のレコード ロックを追加しようとしますが、ステップ 4 でセッション B のトランザクションによって追加されたロックと競合するため、セッション A はブロック状態になり、ロックの取得を待機します。
  • ステップ 6 からわかるように、セッション B のトランザクションは、hero テーブルのクラスター化インデックス内の ID 値 1 のレコードに X タイプのポジティブ レコード ロックを追加しようとしていますが、ステップ 3 でセッション A のトランザクションによって追加されたロックと競合しています。この時点で、セッション A とセッション B のトランザクションは、お互いが保持しているロックを周期的に待機しており、デッドロックが発生します。MySQL サーバーのデッドロック検出メカニズムがこれを検出すると、ロールバックするトランザクションが選択され、クライアントにメッセージが送信されます。

エラー 1213 (40001): ロックを取得しようとしたときにデッドロックが見つかりました。トランザクションを再起動してください。

上記は、ステートメントにどのようなロックが追加されているかという観点からのデッドロック分析です。しかし、実際のアプリケーションでは、どのステートメントがデッドロックを引き起こしたのかまったくわからない場合があります。デッドロックが発生したときに MySQL によって生成されるデッドロック ログに基づいて、どのステートメントがデッドロックを引き起こしたかを逆に特定し、ビジネスを最適化する必要があります。

デッドロックログを表示

InnoDB を設計した人が、デッドロックが最後に発生したときのシステムのロック状態など、InnoDB ストレージ エンジンに関する状態情報を表示するための SHOW ENGINE INNODB STATUS コマンドを提供してくれました。上記の例のデッドロックが発生した場合、次のコマンドを実行します。

mysql> エンジン INNODB ステータスを表示\G
...その他多くの情報は省略されています------------------------
最近検出されたデッドロック
------------------------
2019-06-20 13:39:19 0x70000697e000
*** (1)取引:
トランザクション 30477、アクティブ 10 秒開始インデックス読み取り
使用中の MySQL テーブル 1、ロックされているテーブル 1
LOCK WAIT 3 つのロック構造体、ヒープ サイズ 1160、2 つの行ロック
MySQL スレッド ID 2、OS スレッド ハンドル 123145412648960、クエリ ID 46 localhost 127.0.0.1 ルート統計
更新のために、id = 3 のヒーローから * を選択します
*** (1) このロックが許可されるのを待機しています:
レコード ロック スペース ID 171 ページ番号 3 n ビット 72 インデックス PRIMARY テーブル `dahaizi`.`hero` trx ID 30477 lock_mode X はレコードをロックしますが、ギャップ待機はロックしません
レコード ロック、ヒープ番号 3 物理レコード: n_fields 5; コンパクト フォーマット; 情報ビット 0
 0: 長さ 4; 16 進数 80000003; 昇順 ;;
 1: 長さ 6; 16 進数 000000007517; asc u ;;
 2: 長さ 7; 16 進数 80000001d0011d; 昇順 ;;
 3: 長さ 10; 16 進数 7ae8afb8e8919be4baae; asc z ;;
 4: 長さ 3; 16 進数 e89c80; 昇順 ;;

*** (2)取引:
トランザクション 30478、アクティブ 8 秒開始インデックス読み取り
使用中の MySQL テーブル 1、ロックされているテーブル 1
3 つのロック構造体、ヒープ サイズ 1160、2 つの行ロック
MySQL スレッド ID 3、OS スレッド ハンドル 123145412927488、クエリ ID 47、localhost 127.0.0.1、ルート統計
更新のために、id = 1 のヒーローから * を選択します
*** (2) ロックを保持する:
レコード ロック スペース ID 171 ページ番号 3 n ビット 72 インデックス PRIMARY テーブル `dahaizi`.`hero` trx ID 30478 lock_mode X はレコードをロックしますが、ギャップはロックしません
レコード ロック、ヒープ番号 3 物理レコード: n_fields 5; コンパクト フォーマット; 情報ビット 0
 0: 長さ 4; 16 進数 80000003; 昇順 ;;
 1: 長さ 6; 16 進数 000000007517; asc u ;;
 2: 長さ 7; 16 進数 80000001d0011d; 昇順 ;;
 3: 長さ 10; 16 進数 7ae8afb8e8919be4baae; asc z ;;
 4: 長さ 3; 16 進数 e89c80; 昇順 ;;

*** (2) このロックが許可されるのを待機しています:
レコード ロック スペース ID 171 ページ番号 3 n ビット 72 インデックス PRIMARY テーブル `dahaizi`.`hero` trx ID 30478 lock_mode X はレコードをロックしますが、ギャップ待機はロックしません
レコード ロック、ヒープ番号 2 物理レコード: n_fields 5、コンパクト フォーマット、情報ビット 0
 0: 長さ 4; 16 進数 80000001; 昇順 ;;
 1: 長さ 6; 16 進数 000000007517; asc u ;;
 2: 長さ 7; 16 進数 80000001d00110; 昇順 ;;
 3: 長さ 7; 16 進数 6ce58898e5a487; asc l ;;
 4: 長さ 3; 16 進数 e89c80; 昇順 ;;

*** トランザクションをロールバックします (2)
------------
...他の多くの情報は省略されています

最新のデッドロック情報のみを対象とするため、LATEST DETECTED DEADLOCK セクションを個別に分析します。デッドロック ログ出力が何を意味するのかを行ごとに見てみましょう。

まず最初の文を見てみましょう:

2019-06-20 13:39:19 0x70000697e000

この文は、デッドロックが発生した時刻が 2019-06-20 13:39:19 であり、それに続く 16 進数 0x70000697e000 の文字列が、オペレーティング システムによって現在のセッションに割り当てられたスレッドのスレッド ID を表すことを意味します。

次に、デッドロックが発生したときの最初のトランザクションに関する情報があります。

*** (1)取引:

# トランザクション ID は 30477 です。トランザクションは 10 秒間アクティブ状態になっています。トランザクションによって現在実行されている操作は、「インデックス読み取りを開始しています」です。
トランザクション 30477、アクティブ 10 秒開始インデックス読み取り

# このトランザクションは 1 つのテーブルを使用し、1 つのテーブルをロックします (これはテーブルにテーブル ロックが追加されることを意味するものではありません。テーブルが一貫して読み込まれない限り、ロックする必要があります。ロックの具体的な方法については、ロック ステートメントの分析またはブックレットの章を参照してください)
使用中の MySQL テーブル 1、ロックされているテーブル 1

# このトランザクションは LOCK WAIT 状態にあり、3 つのロック構造 (2 つの行ロック構造、1 つのテーブル レベルの X 型意図ロック構造、ロック構造につ​​いては小冊子で詳しく説明されています) があり、ヒープ サイズはロック構造を格納するために要求されるメモリ サイズです (無視できます)、2 つの行ロック構造があります LOCK WAIT 3 つのロック構造、ヒープ サイズ 1160、2 つの行ロック

# このトランザクションのスレッド ID は 2 (MySQL 独自のスレッド ID) です。オペレーティング システム レベルのスレッド ID は長い数字の文字列です。現在のクエリの ID は 46 (MySQL 内部使用、無視できます) で、ユーザー名とホスト情報もあります。MySQL スレッド ID 2、OS スレッド ハンドル 123145412648960、クエリ ID 46 localhost 127.0.0.1 ルート統計

# このトランザクションをブロックするステートメント select * from hero where id = 3 for update

# このトランザクションが現在取得を待機しているロック:
*** (1) このロックが許可されるのを待機しています:

# 取得するテーブルスペースIDは151、ページ番号は3、つまりヒーローテーブルのPRIMAYインデックスのレコードのロックです(n_bitsはこのページのロック情報を格納するために割り当てられたメモリスペースの文字列で、パンフレットに詳細が記載されています)。ロックタイプはXタイプのレコードですが、ギャップロックではありません。
レコード ロック スペース ID 171 ページ番号 3 n ビット 72 インデックス PRIMARY テーブル `dahaizi`.`hero` trx ID 30477 lock_mode X はレコードをロックしますが、ギャップ待機はロックしません

# ページ内のこのレコードの heap_no は 2 です。具体的なレコード情報は次のとおりです。
レコード ロック、ヒープ番号 3 物理レコード: n_fields 5; コンパクト フォーマット; 情報ビット 0

# これは主キー値 0 です: len 4; hex 80000003; asc ;;

# これは trx_id の隠し列 1: len 6; hex 000000007517; asc u ;;

# これは roll_pointer の隠し列 2: len 7; hex 80000001d0011d; asc ;;

# これは名前の列 3 です: len 10; hex 7ae8afb8e8919be4baae; asc z ;;

# これは国列 4 です: len 3; hex e89c80; asc ;;

この情報から、セッション A のトランザクションは 2 つのレコードのロック構造を生成しましたが、レコードの 1 つに対する X タイプの通常のレコード ロック (rec は取得されますが、gap は取得されません) が取得されなかったことがわかります。ロックされなかったレコードの場所は、テーブルスペース ID が 151、ページ番号が 3、heap_no が 2 です。もちろん、InnoDB を設計した人も、このレコードの詳細を親切に教えてくれました。主キー値は 80000003 で、これは実際に InnoDB の内部ストレージに使用される形式です。これは実際には数字の 3 を表しており、これはトランザクションが、ヒーロー テーブルのクラスター化インデックス内の主キー値が 3 であるレコードの X タイプの通常レコード ロックを取得するのを待機していることを意味します。

次に、デッドロックが発生したときの 2 番目のトランザクションに関する情報があります。

すでにほとんどの情報を紹介しているので、重要なものだけを取り上げます。

*** (2)取引:
トランザクション 30478、アクティブ 8 秒開始インデックス読み取り
使用中の MySQL テーブル 1、ロックされているテーブル 1
3 つのロック構造体、ヒープ サイズ 1160、2 つの行ロック
MySQL スレッド ID 3、OS スレッド ハンドル 123145412927488、クエリ ID 47 localhost 127.0.0.1 ルート統計
更新のために、id = 1 のヒーローから * を選択します

# トランザクションによって取得されたロック情報を示します*** (2) ロックを保持します:
レコード ロック スペース ID 171 ページ番号 3 n ビット 72 インデックス PRIMARY テーブル `dahaizi`.`hero` trx ID 30478 lock_mode X はレコードをロックしますが、ギャップはロックしません
レコード ロック、ヒープ番号 3 物理レコード: n_fields 5; コンパクト フォーマット; 情報ビット 0

# 主キーの値は3です
0: 長さ 4; 16 進数 80000003; 昇順 ;;
1: 長さ 6; 16 進数 000000007517; asc u ;;
2: 長さ 7; 16 進数 80000001d0011d; 昇順 ;;
3: 長さ 10; 16 進数 7ae8afb8e8919be4baae; asc z ;;
4: 長さ 3; 16 進数 e89c80; 昇順 ;;

# トランザクションが取得を待機しているロック情報を示します*** (2) このロックの許可を待機中:
レコード ロック スペース ID 171 ページ番号 3 n ビット 72 インデックス PRIMARY テーブル `dahaizi`.`hero` trx ID 30478 lock_mode X はレコードをロックしますが、ギャップ待機はロックしません
レコード ロック、ヒープ番号 2 物理レコード: n_fields 5、コンパクト フォーマット、情報ビット 0

# 主キーの値は1です
0: 長さ 4; 16 進数 80000001; 昇順 ;;
1: 長さ 6; 16 進数 000000007517; asc u ;;
2: 長さ 7; 16 進数 80000001d00110; 昇順 ;;
3: 長さ 7; 16 進数 6ce58898e5a487; asc l ;;
4: 長さ 3; 16 進数 e89c80; 昇順 ;;

上記の出力から、セッション B のトランザクションが、ヒーロー テーブルのクラスター化インデックス内の主キー値が 3 のレコードに対して X タイプの適切なレコード ロックを取得し、ヒーロー テーブルのクラスター化インデックス内の主キー値が 1 のレコードに対して X タイプの適切なレコード ロックを取得するのを待機していることがわかります (暗黙的な意味は、ヒーロー テーブルのクラスター化インデックス内の主キー値が 1 のレコードに対して X タイプの適切なレコード ロックがセッション A のトランザクションによって取得されたことです)。

最後の部分を見てください:

*** トランザクションをロールバックします (2)

最後に、InnoDB ストレージ エンジンは、セッション B のトランザクションである 2 番目のトランザクションをロールバックすることを決定します。

デッドロック分析のアイデア

1. デッドロック ログを確認するときは、まず、デッドロックしたトランザクションがロックを取得するためにどのステートメントを待機しているかを確認します。

この例では、SESSION A をブロックするステートメントは次のとおりです。

更新のために、id = 3 のヒーローから * を選択します

SESSION B がブロックされるステートメントは次のとおりです。

更新のために、id = 1 のヒーローから * を選択します

次に、独自のビジネス コード内でこれらの 2 つのステートメントが配置されているトランザクション内の他のステートメントを見つけることを忘れないでください。

2. デッドロックが発生したトランザクション内のすべてのステートメントを見つけたら、トランザクションによって取得されたロックと待機されているロックに関する情報を比較して、デッドロック プロセスを分析します。

デッドロック ログから、セッション A が、ヒーロー テーブルのクラスター化インデックスの ID 値 1 のレコードの X タイプの通常レコード ロックを取得していることがわかります (これは、実際にはセッション B が待機しているロックから取得されます)。セッション A のステートメントを見ると、次のステートメントが原因であることがわかります (ステートメント ロック分析に関する 3 つの記事を参照してください)。

更新のために、id = 1 の hero から * を選択します。

さらに、SESSION B は、hero テーブル内のクラスター化インデックス ID 値が 3 のレコードに対して、X タイプの通常のレコード ロックを取得します。SESSION B のステートメントを見ると、次のステートメントが原因であることがわかります (ステートメント ロック分析に関する 3 つの記事を参照)。

更新のために、id = 3 の hero から * を選択します。

次に、セッション A が、ヒーロー テーブル内のクラスター化インデックス ID 値が 3 であるレコードの X タイプの通常レコード ロックを待機していることがわかります。これは、次のステートメントによって発生します。

更新のために、id = 3 の hero から * を選択します。

次に、セッション B が、ヒーロー テーブル内のクラスター化インデックス ID 値が 1 であるレコードの X タイプの通常レコード ロックを待機していることがわかります。これは、次のステートメントによって発生します。

更新のために、id = 1 の hero から * を選択します。

その後、デッドロック ログに基づいて、デッドロック形成プロセス全体が復元されます。

要約する

以上がこの記事の全内容です。この記事の内容が皆様の勉強や仕事に何らかの参考学習価値をもたらすことを願います。123WORDPRESS.COM をご愛顧いただき、誠にありがとうございます。

以下もご興味があるかもしれません:
  • Mysql のデッドロックの表示とデッドロックの除去の詳細な説明
  • MySQLのデッドロックチェック処理の通常の方法
  • MySQLデッドロックの原因と解決策
  • MySQL デッドロック ルーチン: 一意のインデックスの下でのバッチ挿入順序の不一致
  • MySQL デッドロック シナリオ例の分析
  • 魔法のMySQLデッドロックトラブルシューティング記録
  • MySQL データベースのパージデッドロック問題の分析
  • SQLによる分散デッドロックの検出と排除の詳細な説明

<<:  Linux netfilter/iptables の知識ポイントの詳細な説明

>>:  JSはタイムラインの自動再生を実現する

推薦する

WeChatアプレットが検索ボックス機能を実装

この記事の例では、WeChatアプレットの検索ボックス機能を実装するための具体的なコードを参考までに...

JavaScriptの再帰の詳細

目次1. 再帰とは何ですか? 2. 再帰を使って数学の問題を解く1. 1 * 2 * 3 * 4 …...

グループ化されたクエリでのGROUP BYの使用とSQL実行順序の説明

SQL では、GROUP BY は SELECT の結果のデータをグループ化するために使用されます。...

JavaScript でアルゴリズムの複雑さを学ぶ方法

目次概要Big O 表記法とは何ですか?オー(1)の上) (n^2) O(logn) ですの上!)結...

JS 非同期スタック トレース: await が Promise よりも優れている理由

概要async/await と Promise の基本的な違いは、await fn() は現在の関数...

WeChat アプレット wxss で外部 CSS ファイルとアイコンフォントを参照する方法

原因外部ファイルをミニプログラムにインポートする方法は次のとおりです: @import "...

Windows で IP アドレスを指定してサーバーへのリモート アクセスを設定する方法

当社には、外部ネットワークからの干渉を受けることが多いサーバーが多数あります。侵入者はポート 338...

Apache Log4j2 が核レベルの脆弱性と迅速な修正を報告

Apache Log4j2 が核レベルの脆弱性を報告し、スタックリーダーの友人たちは大騒ぎになりまし...

nginx を使用してカナリアリリースをシミュレートする方法

この記事では、ブルーグリーン デプロイメントと、nginx を使用してカナリア リリースを最も簡単な...

Vue.jsのレンダリング関数の使い方の詳しい説明

Vue では、ほとんどの場合、テンプレートを使用して HTML を作成することを推奨しています。ただ...

React のネストされたコンポーネントの構築順序

目次Reactの公式サイトではライフサイクルの説明を見ることができます次に、ネストされたコンポーネン...

VScode設定のリモートデバッグLinuxプログラムの問題を解決する

VScode リモートデバッグ Linux プログラムの問題について見てみましょう。具体的な内容は以...

MySQLトリガーの概念、原理、使用法の詳細な説明

この記事では、例を使用して、MySQL トリガーの概念、原則、および使用方法を説明します。ご参考まで...

WeChatアプレットAmapマルチポイントルート計画プロセス例の詳細な説明

電話Amap API を呼び出す方法は? Amap が https://lbs.amap.com/a...

ハイパーリンクアイコンの仕様: 記事の読みやすさを向上

1. ハイパーリンクアイコンの仕様とは?<br />ハイパーリンクアイコンの仕様は、「C...