出典:パブリックアカウントyangyidba
I.はじめに
デッドロックは、実際には非常に興味深く、やりがいのある技術的な問題です。おそらく、すべてのDBAと一部の開発学生は、作業中にデッドロックに遭遇するでしょう。デッドロックについては、デッドロックを理解したい友達を助けたいと思い、一連のケーススタディを書き続けていきます。
2.ケース分析
2.1ビジネスシナリオ
ビジネス開発の学生は、データを同期したいと考えています。彼らのロジックは、更新操作を通じて更新することです。更新されたレコードによって返されるaffect_rowsが0の場合、挿入ステートメントを呼び出して挿入を初期化します。挿入が失敗した場合、更新操作が再度実行され、複数のセッションの同時操作の場合はデッドロックが発生します。
2.2環境の説明
MySQL5.6.24トランザクション分離レベルはRRです
create table ty (
id int not null primary key auto_increment ,
c1 int not null default 0,
c2 int not null default 0,
c3 int not null default 0,
unique key uc1(c1),
unique key uc2(c2)
) engine=innodb ;
insert into ty(c1,c2,c3)
values(1,3,4),(6,6,10),(9,9,14);
2.3テストケース
2.4デッドロックログ
2018-03-27 17:59:23 0x7f75bf39d700
*** (1) TRANSACTION:
TRANSACTION 1863, ACTIVE 76 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1136, 3 row lock(s), undo log entries 1
MySQL thread id 382150, OS thread handle 56640, query id 28 localhost root update
insert into ty (c1,c2,c3) values(3,4,2)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 28 page no 5 n bits 72 index uc2 of table `test`.`ty` trx id 1863 lock_mode X locks gap before rec insert intention waiting
*** (2) TRANSACTION:
TRANSACTION 1864, ACTIVE 65 sec inserting, thread declared inside InnoDB 5000
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 382125, OS thread handle 40032, query id 62 localhost root update
insert into ty (c1,c2,c3) values(3,4,2)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 28 page no 5 n bits 72 index uc2 of table `test`.`ty` trx id 1864 lock_mode X locks gap before rec
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 28 page no 4 n bits 72 index uc1 of table `test`.`ty` trx id 1864 lock mode S waiting
*** WE ROLL BACK TRANSACTION (2)
2.5デッドロックログを分析する
まず、挿入操作のロックロジックを再度強調する必要があります。
最初の段階:一意性制約チェック、最初にLOCK_S + LOCK_ORDINARYを適用します
2番目のステージ:ステージ1のロックを取得して正常に挿入した後、他の挿入固有のキーの競合を防ぐために、挿入された位置LOCK_INSERT_INTENTIONにGAPロックがあります。
新しいデータが挿入された後:LOCK_X + LOCK_REC_NOT_GAP
挿入操作の場合、一意の制約の競合が発生した場合は、競合する一意のインデックスにS Next-keyLockを追加する必要があります。ここから、RCトランザクション分離レベルでも、Next-Key Lockロックがあり、それによって同時実行がブロックされていることがわかります。ただし、このドキュメントには、競合が検出された一意のインデックスの場合、待機中のスレッドはSロックを取得した後、次のレコードをロックする必要があると記載されていません。ソースコードは、関数row_ins_scan_sec_index_for_duplicateを決定するために使用されます。
次に、ロック解除された互換性マトリックスが必要です。
互換性マトリックスから、次の結論を導き出すことができます。
INSERT操作間で競合は発生しません。
GAP、Next-KeyはInsertをブロックします。
GAPとRecord、Next-Keyは競合しません。
Record and Record、Next-Keyが互いに競合します。
既存の挿入ロックは、追加されるロックを妨げません。
すでに保持されているGAPロックは、挿入意図ロックINSERT_INTENTIONをブロックします。
さらに、GAPロックは、一意のインデックスを介して存在しないレコードを更新または削除するために適用されます。
分析
上記の基本的な知識を理解して、デッドロックログの分析を開始しました。
T1:sess1は一意のキーを使用してデータを更新します。c2= 4が存在しないため、返される影響行は0であり、MySQLは(3,6)間のGAPロックを適用します。
T2:sess2の状況はsess1の状況と同様であり、(3,6)間のGAPロックも適用されます。上記の互換性マトリックスから、2つのGAPロックは競合しません。
T3:sess1は、更新ステートメントに従って影響行を0として返し、挿入操作を実行します。このとき、挿入意図ロックを申請する必要があります。sess2セッションで保持されているGAPロックは、sess1で適用されている挿入意図ロックと競合し、待機します。
テーブルtest.tyのインデックスuc2trx id 1863 lock_mode Xは、rec挿入意図が待機する前にギャップをロックします
T4:sess2はsess1に似ています。updateステートメントによると、影響行は0として返され、挿入操作が実行されます。適用された挿入意図ロックは、sess1の更新ステートメントによって保持されているGAPロックと競合します。sess1(GAPロックを保持)、sess2(GAPロックを保持)、sess1(sess2のGAPロック解除を待機する挿入意図ロック)sess2(sess1のGAPロック解除を待機する挿入意図ロック) は循環待機を構成し、デッドロックにつながります。
2.6解決策
ビジネスシナリオの処理ロジックの観点から、ビジネスは2つの要求、1つは更新、もう1つは挿入を送信してビジネスロジックを完了する必要がありますが、これは友好的で最適化されていません。
実際、開発クラスメートとのコミュニケーション、ビジネスの独立性の確認、重複キーの挿入方法の使用、存在しない場合の挿入、存在する場合の更新、1回の呼び出しで、前の2つの操作の機能を完了し、パフォーマンスを向上させることができます。
3、まとめ
最後に、デッドロックの問題を解決するというアイデアについてお話ししたいと思います:
1.ロックに関する確かな基本知識を持っている。
2.デッドロックログだけで特定のSQL実行状況を判断することは困難です。開発クラスメートと連絡を取り、業務実行SQLロジックを明確にしてから、テストをシミュレートする必要があります。
拡張読書
全文は終わりました。
MySQLをお楽しみください:)
TeacherYeの「MySQLCoreOptimization」クラスがMySQL8.0にアップグレードされました。コードをスキャンして、MySQL8.0の練習の旅を始めてください。