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はタイムラインの自動再生を実現する

推薦する

MySQLのslave_exec_modeパラメータの詳細な説明

今日、slave_exec_modeというパラメータを偶然見ました。マニュアルの説明から、このパラメ...

MYSQL フルバックアップ、マスタースレーブレプリケーション、カスケードレプリケーション、および半同期の概要

MySQL フルバックアップ1. バイナリログを有効にし、データベースから分離して別々に保存する v...

Vue 2つのフィールドの共同検証によりパスワード変更機能を実現

目次1. はじめに2. ソリューションの実装2.1 実装コード2.2 コードの説明2.3 検証結果1...

Dockerfile の一般的なコマンドの概要

構文の構成: 1 注釈情報2 コマンド --- パラメータ [通常は大文字 | 実際には大文字と小文...

Vueは小さなフォーム検証機能を実装します

この記事では、フォーム検証を実装するためのVueの具体的なコードを例として紹介します。具体的な内容は...

親子コンポーネントの通信を解決するための3つのVueスロット

目次序文環境の準備カテゴリコンポーネントアプリのコンポーネント1. デフォルトスロット2. 名前付き...

MySQL/MariaDB ルートパスワードリセットチュートリアル

序文パスワードを忘れることは、よく遭遇する問題です。MySQL または MariaDB データベース...

JavaScriptオブジェクトをマージするさまざまな方法の詳細な説明

目次オブジェクトをマージするさまざまな方法(インターフェースを通じてデータを取得し、それをローカル ...

nginx を使用して http を https に変換するサンプルコード

最近、小さなプログラムを書いています。その小さなプログラムの公式ウェブサイトはhttpsを使用する必...

CSS3 で実装された価格表

結果: 実装コードhtml <div id="価格表" class=&qu...

MySQL のロングトランザクション例の詳細な説明

序文: 「MySQL 入門」シリーズの記事は終了しました。今後も引き続き MySQL に焦点を当て、...

MySQLのページング制限のパフォーマンス問題についての簡単な説明

MySQL ページング クエリは通常、制限を通じて実装されます。 limit は 1 つまたは 2 ...

nginx を使用して同じドメイン名で複数の Vue プロジェクトをデプロイし、リバース プロキシを使用する方法

効果現在、プロジェクトは2つ(project1、project2)あり、nginxに付属するinde...

MySql のクラッシュとサービスの起動失敗の解決策

私は長い間PHPに触れてきましたが、インストール環境は非常に不慣れです。多くの問題に遭遇しました。B...

CentOS7 のシステム サービスに Nginx を追加する方法

導入コンパイル、インストール、問題の解決後、Nginx は正常に動作していますが、現時点では Ngi...