MySQL を使用するとデッドロックの問題が発生する可能性が高く、これは非常に頭の痛い問題です。この記事では、デッドロックについて紹介し、一般的なデッドロックのケースを分析して説明し、デッドロックを可能な限り回避する方法についていくつかの提案を提供します。
1. デッドロックとは
デッドロックは同時システムでよくある問題であり、データベース MySQL の同時読み取りおよび書き込みリクエストのシナリオでも発生します。トランザクションが 2 つ以上ある場合、双方が既に保持しているロックを相手が解放するのを待っているか、ロックの順序が不一致でロック リソースを待つサイクルが発生するため、「デッドロック」が発生します。一般的なエラー メッセージは次のとおりですDeadlock found when trying to get lock...
。
たとえば、トランザクション A は X1 ロックを保持し、X2 ロックを適用し、トランザクション B は X2 ロックを保持し、X1 ロックを適用します。A トランザクションと B トランザクションはロックを保持し、相互に保持しているロックを申請して循環待機に入り、その結果デッドロックが発生します。
上図に示すように、右側の 4 台の車両のリソース要求にはループ現象、つまり無限ループが発生し、デッドロックが発生します。
デッドロックの定義から、MySQL デッドロックのいくつかの要素は次のとおりです。
-
2つ以上のトランザクション
-
各トランザクションはすでにロックを保持しており、新しいロックを申請します。
-
ロック リソースは同時に同じトランザクションによってのみ保持できるか、互換性がありません
-
ロックの保持とロックの申請により、トランザクションは循環的に相互に待機します。
2. InnoDB ロックの種類
デッドロックを分析するには、InnoDB のロック タイプを理解する必要があります。
MySQL InnoDB エンジンは標準を実装しています。行级别锁:共享锁( S lock ) 和排他锁 ( X lock )
異なるトランザクションは、同じ行レコードに同時に S ロックを追加できます。
トランザクションが行レコードに X ロックを追加すると、他のトランザクションは S ロックまたは X ロックを追加できず、ロック待機が発生します。
トランザクション T1 が行 r の S ロックを保持している場合、別のトランザクション T2 が r のロックを要求すると、次の処理が実行されます。
T2 の S ロック要求は直ちに許可され、その結果、T1 と T2 の両方が行 r の S ロックを保持します。
X ロックの T2 リクエストはすぐには許可されません
T1 が r の X ロックを保持している場合、r の X および S ロックに対する T2 の要求はすぐには許可されず、X ロックはどのロックとも互換性がないため、T2 は T1 が X ロックを解放するまで待つ必要があります。共有ロックと排他ロックの互換性は次のとおりです。
2.1、ギャップロック(ギャップロック)
ギャップロックはギャップをロックして挿入を防ぎます。インデックス列に 2、4、8 の 3 つの値があるとすると、4 をロックすると、2 つのギャップ (2,4) と (4,8) も同時にロックされます。他のトランザクションは、これら 2 つのギャップの間にインデックス値を持つレコードを挿入できません。ただし、ギャップ ロックには例外があります。
インデックス列が一意のインデックスの場合、このレコードのみ (行ロックのみ) がロックされ、ギャップはロックされません。
ジョイント インデックスの場合、それが一意のインデックスである場合、where 条件にジョイント インデックスの一部のみが含まれている場合でも、ギャップ ロックは追加されます。
2.2、ネクストキーロック
ネクストキー ロックは、実際には、このレコードの前の行ロックとギャップ ロックの組み合わせです。インデックス値が 10、11、13、20 であると仮定すると、考えられる次のキー ロックには次のものがあります。
(負の無限大,10],(10,11],(11,13],(13,20],(20,正の無限大)
RR 分離レベルでは、InnoDB は主に問題を防ぐためにネクスト キー ロックを使用します幻读
。
2.3. インテンションロック(インテンションロック)
複数粒度のロックをサポートするために、InnoDB では行ロックとテーブル ロックを同時に存在させることができます。さまざまな粒度でのロック操作をサポートするために、InnoDB は Intention Lock と呼ばれる追加のロック メソッドをサポートしています。インテンション ロックは、ロックされたオブジェクトを複数のレベルに分割します。インテンション ロックは、トランザクションがより細かい粒度でロックすることを意味します。インテント ロックには 2 つのタイプがあります。
意図的な共有ロック ( IS ): トランザクションはテーブル内の特定の行に共有ロックを追加することを目的としています。
意図的な排他ロック (IX): トランザクションはテーブル内の一部の行を排他ロックすることを目的としています。
InnoDB ストレージ エンジンは行レベルのロックをサポートしているため、インテント ロックはテーブル全体のスキャン以外のリクエストを実際にはブロックしません。テーブルレベルのインテントロックと行レベルのロックの互換性は次のとおりです。
2.4、インテンションロックの挿入(インテンションロックの挿入)
挿入意図ロックは、レコードの行を挿入する前に設定されるギャップ ロックです。このロックは、挿入モードの信号を解放します。つまり、複数のトランザクションが同じインデックス ギャップに挿入される場合、同時に挿入する必要はありません。隙間の位置でお互いを待ちます。列のインデックス値が 2 と 6 であると仮定すると、2 つのトランザクションの挿入位置が異なっていれば (たとえば、トランザクション A は 3 を挿入し、トランザクション B は 4 を挿入)、それらを同時に挿入できます。 。
2.5. ロックモード互換性マトリックス
水平方向は保持されているロック、垂直方向は要求されているロックです。
3. デッドロックログを読む
特定のケースを分析する前に、まずデッドロック ログの読み取り方法を理解し、デッドロック ログの情報をデッドロックの問題の解決にできる限り活用しましょう。
次のテスト ケースのデータベース シナリオは次のとおりです。MySQL 5.7 事务隔离级别为 RR
テーブルの構造とデータは次のとおりです。
テストケースは次のとおりです。
show Engine innodb status を実行すると、最新のデッドロック ログを表示できます。
3.1. ログ分析は次のとおりです。
1.***** (1) トランザクション: トランザクション 2322、アクティブ インデックス読み取り開始 6 秒
トランザクション番号は 2322 で、6 秒間アクティブで、インデックス読み取りの開始は、トランザクション ステータスがインデックスに従ってデータを読み取り中であることを示します。その他の一般的な状態は次のとおりです。
mysql tables in use 1
現在のトランザクションがテーブルを使用していることを示します。
locked 1
テーブルにテーブル ロックがあることを示します (DML ステートメントの場合は LOCK_IX)。
LOCK WAIT 2 ロック構造体、ヒープ サイズ 1136、1 行ロック
LOCK WAIT
ロックを待機していることを示し、2 lock struct(s)
trx->trx_locks ロック リストの長さが 2 であることを示し、各リンク リスト ノードは、テーブル ロック、レコード ロック、自己増加ロックなど、トランザクションによって保持されるロック構造を表します。この使用例では、2locks は IX ロックと lock_mode X (ネクストキー ロック) を意味します。
1 row lock(s)
現在のトランザクションによって保持されている行レコード ロック/ギャップ ロックの数を示します。
MySQL スレッド ID 37、OS スレッド ハンドル 140445500716800、クエリ ID 1234 127.0.0.1 ルート更新
MySQL thread id 37
トランザクションを実行しているスレッドの ID が 37 (つまり、show processlist、表示される ID) であることを示します。
delete from student where stuno=5
トランザクション 1 によって実行されている SQL を示します。さらに不快なのは、 show engine innodb status
完全な SQL を表示できないことです。通常、現在ロックを待機している SQL が表示されます。
***** (1) このロックが許可されるのを待っています:
RECORD LOCKS スペース ID 11 ページ番号 5 n ビット 72 テーブル cw****.**** のインデックス idx_stuno 学生 trx id 2322 lock_mode X 待機中
RECORD LOCKS はレコード ロックを意味します。この項目は、トランザクション 1 がテーブル Student 上の idx_stuno の X ロック (この場合は実際には Next-Key Lock) を待っていることを意味します。
トランザクション 2 のログは、上記の分析と似ています。
2.***** (2) ロックを保持します:
RECORD LOCKS スペース ID 11 ページ番号 5 n ビット 72 テーブル cw****.**** のインデックス idx_stuno trx id 2321 lock_mode X
これは、トランザクション 2 のstudent(stuno,score) value(2,10) への挿入が、a=5 のロック モード X を保持していることを示しています。
LOCK_gap ですが、stuno=5 の学生からの削除は表示されません。
これは、DBA がログのみに基づいてデッドロックを分析することが難しいという問題の根本原因でもあります。
3.***** (2) このロックが許可されるのを待っています:
RECORD LOCKS スペース ID 11 ページ番号 5 n ビット 72 テーブル cw****.**** のインデックス idx_stuno trx id 2321 lock_mode X ロック ギャップ前に挿入意図を挿入します。
トランザクション 2 の挿入ステートメントが、インテント ロックの挿入を待機していることを示します。 lock_mode X ロック ギャップ前に、rec 挿入インテンションを待機します ( LOCK_X + LOCK_REC_gap )。
4. 古典的な事例分析
4.1. トランザクションの同時挿入の一意のキーの競合
テーブルの構造とデータは次のとおりです。
テストケースは次のとおりです。
ログ分析は次のようになります。
-
トランザクション T2 t7(id,a) 値 (26,10) への挿入 ステートメントの挿入は成功し、a=10 を保持します
排他行锁( Xlocks rec but no gap )
-
トランザクション T1 が t7(id,a) 値 (30,10) に挿入されます。T2 の最初の挿入ですでに a=10 レコードが挿入されているため、トランザクション T1 挿入 a=10 では一意のキーの競合が発生します。唯一の競合 インデックス プラス S ネクスト キー ロック (つまり、ロック モード S 待機中) これは、
间隙锁
ロック (,10]、(10,20]) に適用されるギャップ領域です。 -
トランザクション T2 t7(id,a) 値 (40,9) への挿入 このステートメントによって挿入された a=9 の値はトランザクション T1 に適用されるため、トランザクション T2 の 2 番目の挿入ステートメントは、トランザクションの解放を待つ必要があり ます
gap 锁4-10之间
。S-Next-key Lock 锁
トランザクション T1 のログに、Lock_mode X locks gap before rec insert 意図の待機が表示されます。
4.2. 最初に更新してから挿入する場合の同時デッドロックの問題
データのないテーブル構造は次のとおりです。
テストケースは次のとおりです。
デッドロック分析:
2 つのトランザクション更新のレコードが存在せず、連続して取得されていることがわかります。间隙锁( gap 锁)
ギャップ ロックには互換性があるため、更新リンクはブロックされません。両方ともギャップ ロックを保持し、挿入をめぐって競合します意向锁
。他のセッションがギャップ ロックを保持している場合、現在のセッションは挿入意図ロックを適用できず、デッドロックが発生します。
5. デッドロックを可能な限り回避する方法
-
インデックスを合理的に設計し、高度に差別化された列を複合インデックスの前に配置して、ビジネス SQL が可能な限りインデックスを通過できるようにします
定位更少的行,减少锁竞争
。 -
ビジネスロジックSQLの実行順序を調整し、トランザクション前に長時間ロックを保持する更新/削除SQLを回避します。
-
これを避け
大事务
、大きなトランザクションを複数の小さなトランザクションに分割して処理するようにすると、小さなトランザクションでロックが競合する可能性も低くなります。 -
固定的顺序
テーブルと行にアクセスします。たとえば、データを更新する 2 つのトランザクションの場合、データを更新するトランザクション A の順序は 1、2 であり、データを更新するトランザクション B の順序は 2、1 です。これにより、デッドロックが発生する可能性が高くなります。 -
同時実行性が比較的高いシステムでは、特にトランザクションでは明示的にロックしないでください。select ... for update ステートメントなど、トランザクション内にある場合
(运行了 start transaction 或设置了autocommit 等于0)
、見つかったレコードはロックされます。 -
主键/索引
範囲検索を行うとロック競合が発生する可能性が高くなります。データベースを使用して余分な量の計算作業を行わないでください。たとえば、一部のプログラムでは「select ... where ... order by rand();」のようなステートメントが使用されますが、このようなステートメントではインデックスが使用されないため、テーブル全体のデータがロックされます。 -
SQL とテーブルの設計を最適化して、同時に使用されるリソースが多すぎる状況を減らします。たとえば、
减少连接的表
複雑な SQL を分解
複数の単純な SQL に変換します。