MySQL デッドロック シナリオ例の分析

MySQL デッドロック シナリオ例の分析

序文

最近、MySQL で RR レベルでデッドロック問題に遭遇しました。興味深いと思ったので、調べてみて記録に残しました。

関連する知識ポイント: 共有ロック、排他ロック、意図ロック、ギャップ ロック、挿入意図ロック、ロック待機キュー

シナリオ

分離レベル: 繰り返し読み取り

表の構造は次の通りです

テーブルtを作成(
 id int NULLでない主キー AUTO_INCREMENT、
 NULLでない整数 デフォルト0、
 b varchar(10) NULLでないデフォルト ''
 c varchar(10) NULLでないデフォルト ''
 ユニークキー uniq_a_b(a,b)、
 ユニークキー uniq_c(c)
);

データの初期化

t(a,b,c) に値(1,'1','1') を挿入します。

2つのセッションA/Bがあり、2つのトランザクションが次の順序で実行されます。

消す

  • Bが4を実行した後も、すべて正常のままです
  • Aが5を実行するとブロックされる
  • 次にBは6を実行し、Bはデッドロックを報告し、Bはロールバックし、Aはデータを挿入する

デッドロック情報はshow engine innodb statusで確認できます。ここでは掲載しません。まず、いくつかのロックの概念を説明し、次にデッドロックのプロセスを理解します。

共有 (S) ロック/ミューテックス (X) ロック

  • 共有ロックによりトランザクションはレコードを読み取ることができる
  • ミューテックスロックによりトランザクションはレコードの読み取りと書き込みが可能になる

これら 2 種類のロックは、行ロックおよびギャップ ロックと混在させることができます。複数のトランザクションが同時に S ロックを保持できますが、X ロックを保持できるのは 1 つのトランザクションだけです。

意図ロック

テーブル ロック (ロック モードでもある) は、トランザクションが対応するテーブルのレコードに S ロックまたは X ロックを追加しようとしていることを示します。 SELECT ... LOCK IN SHARE MODE は、レコードに S ロックを追加する前にテーブルに IS ロックを追加し、SELECT ... FOR UPDATE は、レコードに X ロックを追加する前にテーブルに IX ロックを追加します。

これはMySQLのロック最適化戦略です。意図ロックの最適化ポイントがよく分かりません。アドバイスをお願いします。

2つのロックの互換性は次のとおりです

行ロック

とても簡単です。対応する行をロックするだけです。たとえば、更新、更新の選択、削除などでは、関係する行に行ロックが追加され、他のトランザクションが動作しないようにします。

ギャップロック

RR 分離レベルでは、ファントム リードを防ぐために、レコード自体に加えて、レコードの両側のギャップにもギャップ ロックを追加する必要があります。
たとえば、列 a に共通インデックスがあり、レコード 1、5、10 の 3 つがある場合、 select * from t where a=5 for updateレコード 5 に行ロックを追加するだけでなく、ギャップ (1,5) と (5,10) にギャップ ロックを追加して、他のトランザクションが値 5 のデータを挿入してファントム読み取りが発生するのを防ぎます。
a の通常のインデックスがユニーク インデックスになると、値がユニークになり、 select * from t where a=5 for update 2 つのレコードを読み取ることができないため、ギャップ ロックは不要になります。

ギャップ ロックは互いに互換性があります。相互に排他的である場合、トランザクション A は左半分 (1,5) を保持し、トランザクション B は右半分 (1,10) を保持するため、前の例のレコード a=5 が削除されると、理論的には左と右のギャップ ロックが新しいロック (1,10) にマージされるはずですが、この新しい大規模ロックは誰のものになるのでしょうか。そのため、ギャップロックはSギャップロックでもXギャップロックでも互いに互換性があります。

意図ロックを挿入

挿入意図ロックは、実際には特別なギャップ ロックです。ギャップ ロックの前述の説明から、2 つのトランザクションは実際の挿入の前に一定期間ギャップ ロックを保持できますが、実際の挿入アクションをロックすることはできないことがわかります。実際の挿入の前に、MySQL は対応するレコードの挿入意図ロックを取得して、ギャップに値を挿入する意図を示すことも試みます。

挿入意図ロックとギャップ ロックは相互に排他的です。たとえば、トランザクション 1 がギャップ (1,5) をロックする場合、トランザクション 2 は a=3 の挿入意図ロックを取得できないため、ロックを待機する必要があります。

デッドロックプロセス分析

次に、前の例のデッドロックプロセスを分析します。まず、show engine innodb statusを見てください。

 *** (1)取引:
トランザクション 5967、アクティブ 8 秒挿入
使用中の MySQL テーブル 1、ロックされているテーブル 1
LOCK WAIT 3 ロック構造体、ヒープ サイズ 1136、2 行ロック、UNDO ログ エントリ 1
MySQL スレッド ID 9、OS スレッド ハンドル 140528848688896、クエリ ID 537 192.168.128.1 ルート更新
t(a,b) に値(0,'0') を挿入する
*** (1) このロックが許可されるのを待機しています:
レコード ロック スペース ID 64 ページ番号 4 n ビット 72 インデックス uniq_a_b テーブル `t2`.`t` trx ID 5967 lock_mode X ロック レコード挿入の前のギャップ 意図 待機
レコード ロック、ヒープ番号 2 物理レコード: n_fields 3; コンパクト フォーマット; 情報ビット 0
 0: 長さ 4; 16 進数 80000001; 昇順 ;;
 1: 長さ 1; 16 進数 31; 昇順 1;;
 2: 長さ 4; 16 進数 80000001; 昇順 ;;

*** (2)取引:
トランザクション 5968、アクティブ 7 秒挿入中
使用中の MySQL テーブル 1、ロックされているテーブル 1
ロック構造体 3 個、ヒープ サイズ 1136、行ロック 2 個、UNDO ログ エントリ 1
MySQL スレッド ID 8、OS スレッド ハンドル 140528848484096、クエリ ID 538 192.168.128.1 ルート更新
t(a,b) に値(0,'0') を挿入する
*** (2) ロックを保持する:
レコード ロック スペース ID 64 ページ番号 4 n ビット 72 テーブル `t2`.`t` のインデックス uniq_a_b trx ID 5968 lock_mode X ロック レコード前のギャップ
レコード ロック、ヒープ番号 2 物理レコード: n_fields 3; コンパクト フォーマット; 情報ビット 0
 0: 長さ 4; 16 進数 80000001; 昇順 ;;
 1: 長さ 1; 16 進数 31; 昇順 1;;
 2: 長さ 4; 16 進数 80000001; 昇順 ;;

*** (2) このロックが許可されるのを待機しています:
レコード ロック スペース ID 64 ページ番号 4 n ビット 72 インデックス uniq_a_b テーブル `t2`.`t` trx ID 5968 ロック モード X ロック レコード挿入の前のギャップ 意図 待機
レコード ロック、ヒープ番号 2 物理レコード: n_fields 3; コンパクト フォーマット; 情報ビット 0
 0: 長さ 4; 16 進数 80000001; 昇順 ;;
 1: 長さ 1; 16 進数 31; 昇順 1;;
 2: 長さ 4; 16 進数 80000001; 昇順 ;;

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

セッション A (つまり、TRANSACTION 5967) はレコード (a=1、b='1') の前の挿入意図ロックを待機しており、セッション B (つまり、TRANSACTION 5968) はレコード (a=1、b='1') の前のギャップ ロックを保持していますが、挿入意図ロックも待機しています。これは一体何ですか?変じゃないですか?

プロセスを最初から分析する

  1. AとBがそれぞれ取引を開始する
  2. Aはまずselect * from t where a=0 and b='0' for update;が、レコードが存在しないため、排他ギャップロック(-∞,1)になります。
  3. 次に、B はselect * from t where a=0 and b='0' for update;実行し、最初に IX ロックを追加します。レコードが存在しないため、排他ギャップ ロック (-∞,1) が追加されます。ただし、ギャップ ロックは互いに互換性があるため、ブロックは発生しません。
  4. Aはinsert into t(a,b) values(0,'0');を実行します。このとき、実際に挿入を開始するには、Aは(0,'0')に対する挿入意図ロックを取得する必要があります。これはBが保持している排他ギャップロック(-∞,1)と競合するため、ロックは待機し、レコード(0,'0')のロック待機キューに入ります(レコードは存在しませんが)
  5. B は insert insert into t(a,b) values(0,'0'); 。B は排他ギャップ ロック (-∞,1) を保持していますが、A もそれを保持していることがわかります。そのため、B は待機キューに入り、A がそれを解放するのを待ちます。
  6. ディン、デッドロック発生

デッドロック情報の解釈

トランザクション 1 (トランザクション 5967)、テーブル t2.t trx id 5967 のロック インデックス uniq_a_b の取得を待機しています。lock_mode X は、rec 挿入意図の待機前にギャップをロックします。つまり、一意のインデックス uniq_a_b に対する挿入意図ロックです (lock_mode X は、rec 挿入意図の待機前にギャップをロックします)。
ロック境界は

 0: 長さ 4; 16 進数 80000001; 昇順 ;;
 1: 長さ 1; 16 進数 31; 昇順 1;;
 2: 長さ 4; 16 進数 80000001; 昇順 ;;

2行のレコードを示します

  • 0と1はuniq_a_bの値を表します。a=1、b=0x31(つまり、ASCIIコードの「1」)
  • a=1,b='1' は主キー id=1 に対応します。InnoDB のインデックス構造により、セカンダリ インデックス (非主キー インデックス) は主キー インデックスを指し、主キー インデックスはデータを指すため、主キーにインデックスを付ける必要があります。

0x80000000のint値のビットORについては、理由がよくわかりません。専門家の説明が必要です。

トランザクション 2 (トランザクション 5968) は、テーブル t2.t trx id 5968 lock_mode X のギャップ ロック インデックス uniq_a_b を保持し、rec の前にギャップをロックして、挿入意図ロック インデックス uniq_a_b を待機します。テーブル t2.t trx id 5968 lock_mode X は、rec の挿入意図の前にギャップをロックするため、デッドロックが発生します。

原則として、InnoDB エンジンはロールバック コストが最も低いトランザクションをロールバックしますが、具体的な基準はあまり明確ではありません (ここでも、大物が必要です)。ここでは、InnoDB はトランザクション 2 をロールバックすることを選択します。この時点でデッドロックプロセス分析は完了する。

もう一つ

まだ終わってませんよ。 。 。テーブル構造が

テーブルtを作成(
 id int NULLでない主キー AUTO_INCREMENT、
 NULLでない整数 デフォルト0、
 b varchar(10) NULLでないデフォルト ''
 c varchar(10) NULLでないデフォルト ''
 ユニークキー uniq_c(c)、
 ユニークキー uniq_a_b(a,b)
);
t(a,b,c) に値(1,1,1) を挿入します。

uniq_a_b の前に c に一意のインデックス uniq_c を置くだけで、最終的なデッドロック情報が変わります。

 *** (1)取引:
トランザクション 5801、アクティブ 5 秒挿入
使用中の MySQL テーブル 1、ロックされているテーブル 1
LOCK WAIT 4 ロック構造体、ヒープ サイズ 1136、3 行ロック、UNDO ログ エントリ 1
MySQL スレッド ID 5、OS スレッド ハンドル 140528848688896、クエリ ID 380 192.168.128.1 ルート更新
t2(a,b) に値(0,'0') を挿入する
*** (1) このロックが許可されるのを待機しています:
レコード ロック スペース ID 56 ページ番号 5 n ビット 72 テーブル `t2`.`t2` のインデックス uniq_a_b trx ID 5801 lock_mode X ロック レコード挿入の前のギャップ 意図 待機
レコード ロック、ヒープ番号 2 物理レコード: n_fields 3; コンパクト フォーマット; 情報ビット 0
 0: 長さ 4; 16 進数 80000001; 昇順 ;;
 1: 長さ 1; 16 進数 31; 昇順 1;;
 2: 長さ 4; 16 進数 80000001; 昇順 ;;

*** (2)取引:
トランザクション 5802、アクティブ 4 秒挿入
使用中の MySQL テーブル 1、ロックされているテーブル 1
ロック構造体 3 個、ヒープ サイズ 1136、行ロック 2 個、UNDO ログ エントリ 1
MySQL スレッド ID 6、OS スレッド ハンドル 140528848484096、クエリ ID 381 192.168.128.1 ルート更新
t2(a,b) に値(0,'0') を挿入する
*** (2) ロックを保持する:
レコード ロック スペース ID 56 ページ番号 5 n ビット 72 テーブル `t2`.`t2` のインデックス uniq_a_b trx ID 5802 lock_mode X ロック レコード前のギャップ
レコード ロック、ヒープ番号 2 物理レコード: n_fields 3; コンパクト フォーマット; 情報ビット 0
 0: 長さ 4; 16 進数 80000001; 昇順 ;;
 1: 長さ 1; 16 進数 31; 昇順 1;;
 2: 長さ 4; 16 進数 80000001; 昇順 ;;

*** (2) このロックが許可されるのを待機しています:
レコード ロック スペース ID 56 ページ番号 4 n ビット 72 テーブル `t2`.`t2` のインデックス uniq_c trx ID 5802 ロック モード S 待機中
レコード ロック、ヒープ番号 3 物理レコード: n_fields 2; コンパクト フォーマット; 情報ビット 0
 0: 長さ 0; 16 進数; 昇順;;
 1: 長さ 4; 16 進数 80000002; 昇順 ;;

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

トランザクション 2 が待機しているロックは、以前の挿入意図ロックから共有ロックに変更されました。なんてこった?

ソースコードを読んでいないので、現象から推測することしかできません。テーブル構造内のcのユニークインデックスは(a,b)の前にあり、挿入時にcの値が指定されていないため、デフォルト値0が使用されます。InnoDBは、まず0のレコードがあるかどうかを確認する必要があります。ある場合は、ユニークキーの競合を報告するため、まずSロックを追加する必要がありますが、レコード(0,'0')にはすでにIXロックがあります。上記の互換性マトリックスを見ると、SロックとIXロックは相互に排他的であるため、ロックして待機することしかできません。

要約する

単純な選択と挿入のステートメントのように見えますが、その下には複雑なロック メカニズムが設計されています。これらのロック メカニズムを理解することは、効率的な SQL を書くのに役立ちます (少なくとも正しい 😂)

残る問題:

  1. インテンションロックの最適化ポイントは何ですか?
  2. ロック情報において、行レコードのビット OR の 0x80000000 とは何ですか?
  3. ロックの排他性を決定する順序。シナリオ 1 では、(0,'0') に互換性のあるギャップ ロックがあり、待機キューにロックがあります。どちらを最初に決定する必要がありますか。
  4. InnoDB のトランザクション ロールバック コストを計算するアルゴリズム

参考文献

  • http://hedengcheng.com/?p=771
  • https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html#innodb-insert-intention-locks
  • 次キーロック
  • https://dev.mysql.com/doc/refman/5.7/en/innodb-information-schema-understanding-innodb-locking.html

要約する

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

以下もご興味があるかもしれません:
  • 異なるインデックスを更新してMySQLのデッドロックルーチンを解決する
  • ユニークインデックスの S ロックと X ロックによる MySQL デッドロック ルーチンの理解
  • MySQL (InnoDB) がデッドロックを処理する方法の詳細な説明
  • MySQL における楽観的ロックと悲観的ロックの例
  • Mysqlは実行中のトランザクションを照会し、ロックを待機する方法
  • MySQLの関連ロックについての簡単な理解

<<:  JavaScript で Baidu Maps API にアクセスする方法と手順

>>:  CentOS8 でローカル yum ソースを構成するための詳細なチュートリアル

推薦する

MySQL Innodbの主な機能挿入バッファ

目次挿入バッファとは何ですか?挿入バッファのトリガー条件は何ですか?なぜ一意のインデックスにできない...

CentOS7 で MySQL のスケジュールされた自動バックアップを実装する方法

実稼働環境で起こる最も嬉しいことは、シナリオによっては、更新または削除時にパラメータを無視せざるを得...

JavaScript の基礎: スコープ

目次範囲グローバルスコープ関数のスコープもし、スイッチ、のために、その間ブロックスコープスコープチェ...

Vue Element-ui フォーム検証ルールの実装

目次1. はじめに2. ルール検証の入力モード2.1 サンプルコード2.2、フォーム項目2.3. 小...

Linux でマウントされたファイルシステムの種類を表示する方法

序文ご存知のとおり、Linux は ext4、ext3、ext2、sysfs、securityfs、...

mybatis-plusページングパラメータが渡された後、SQLのwhere条件にはページング情報操作の制限がありません

2時間近くかけて、さまざまな方法を試しました。後で、whereでフィルタリングした後のデータ量が1ペ...

JavaScript の onblur および onfocus イベントの詳細な説明

HTML ページでは、ボタンやテキスト ボックスなどの視覚要素にフォーカスを設定したり、フォーカスを...

LinuxはRsync+Inotifyを使用してローカルとリモートのデータのリアルタイム同期を実現します。

0x0 テスト環境本社本番サーバーと支社バックアップサーバーはリモートデータバックアップが必要です...

CSS を使用して物流の進行状況のスタイルを実装するためのサンプルコード

効果: CSS スタイル: <スタイル タイプ="text/css">...

Nodeはリクエスト追跡にasync_hooksモジュールを使用します

async_hooks モジュールは、Node.js バージョン 8.0.0 に正式に追加された実験...

WeChatミニプログラムをTencent Mapsに接続する2つの方法

最近、WeChat アプレットを作成しているのですが、いくつか問題が発生しました。インターネットでい...

TypeScript の条件型に関する詳細な読書と実践記録

目次ジェネリック型での条件型の使用ツールタイプ脱出ポッド矢印関数で条件型を使用する型推論による条件型...

Tomcat で複数の war パッケージを展開する方法と手順

1 背景JDK1.8-u181とTomcat8.5.53がインストールされました。インストール後、環...

CSSは親要素の下の最初の子要素を選択します(:first-child)

序文最近、プロジェクトで :first-child を使用したのですが、すぐに思いつきました。これは...

最小限の展開で CentOS8 に OpenStack Ussuri をインストールする方法の詳細なチュートリアル

CentOS8 に最小限のデプロイメントで OpenStack Ussuri をインストールするため...