出典:パブリックアカウントyangyidba
I.はじめに
デッドロックは、実際には非常に興味深く、やりがいのある技術的な問題です。おそらく、すべてのDBAと一部の開発者は、作業の過程でデッドロックに遭遇するでしょう。デッドロックについては、デッドロックを理解したい友達を助けたいと思い、一連のケーススタディを書き続けていきます。この記事は、製造プロセスのデッドロックケースから派生しています。
2.背景知識
公式文書[1]は次のように述べています。
「一意のキーに衝突がない場合、REPLACEはINSERTのように実行されます。それ以外の場合は、置換される行に排他的な次のキーのロックが配置されます。」
「一意のキーの競合がない場合、置換操作と挿入ロックは同じです。ただし、一意のキーの競合がある場合、システムは、replaceステートメントの実行時にレコードにLOCKX次のキーロックを追加します。」
上記の翻訳が比較的簡単だと思われる場合は、次の紹介[2]をご覧ください。
create table t1(
a int auto_increment primary key,
b int,
c int,
unique key (b));
replace into t1(b,c) values (2,3)
ステップ1通常の挿入ロジック
最初にクラスター化されたインデックスを挿入します。上記の例では、列aは自動インクリメント列です。明示的に指定されていないため、各挿入の前に競合しない新しい値が生成されます。
次に、セカンダリインデックスbを挿入します。これが唯一のインデックスであるため、重複キーをチェックするときにレコードロックを追加します。タイプはLOCK_Xです。
通常のINSERT操作の場合、重複キーをチェックする必要がある場合はLOCK_Sロックを追加し、ReplaceintoまたはINSERT..ONDUPLICATE操作の場合はLOCK_Xレコードロックを追加します。レコードがすでに存在する場合、エラーDB_DUPLICATE_KEYが返されます。
ステップ2エラーを処理する
前のステップで重複キーが検出されたため、最初のステップで挿入されたクラスター化インデックスレコードをロールバックする必要があります。
ステップ3変換操作
InnoDBレイヤーからサーバーレイヤーへの戻りに失敗し、重複キーエラーを受け取った後、最初に一意のキーが競合するインデックスを取得し、競合するインデックスレコード(およびクラスター化されたインデックスレコード)をロックします。
次に、変換モードを確認して競合を解決します。
ukの競合するインデックスが最後の一意のインデックスであり、外部キー参照がなく、削除トリガーがない場合は、UPDATEROWを使用して競合を解決します。
それ以外の場合は、DELETE ROW + INSERT ROWを使用して競合を解決します。プライマリキーの競合の場合は、最初に削除されます。
ステップ4レコードを更新する
この例では、aがプライマリキーであり、クラスタ化されたインデックスとセカンダリインデックスの更新は、削除と新しいレコードの挿入をマークすることによって行われます。クラスター化インデックスの場合、PK列が変更されたため、クラスター化インデックスレコードの削除と挿入を使用して更新します。セカンダリ一意キーインデックスについては、削除+挿入をマークするのと同じ方法が採用されています。
3、ケース分析
3.1テスト環境を準備する
トランザクション分離レベルREPEATABLEREAD
データの準備
create table ix(id int not null auto_increment,
a int not null ,
b int not null ,
primary key(id),
idxa(a)
) engine=innodb default charset=utf8;
insert into ix(a,b) valuses(1,1),(5,10),(15,12);
デッドロックシナリオ
3.2プロセス分析
ステートメントを実行するたびに、show innodb engine statusを実行して、トランザクションのステータスを表示します。
replace into ix(a、b)values(5,8)を実行するためのトランザクションログは次のとおりです。
---TRANSACTION 1872, ACTIVE 46 sec
4 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2
MySQL thread id 1156, OS thread handle 0672, query id 114 localhost msandbox
分析
レコードa = 5がすでに存在するため、ix(a、b)値(5,8)に置き換えます。レコードは更新され、レコードは次のキーロックで追加されます。レコードロック、ギャップロック、
トランザクションは2つの取り消しを生成し、4つのロックとIXロック、a = 5行の場合は1行のロック、1-5から5-15の間の2つのギャップロックを保持します。
replace into ix(a、b)values(8,10)を実行するためのトランザクションログは次のとおりです。
---TRANSACTION 1873, ACTIVE 3 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s),
undo log entries 1
MySQL thread id 1155, OS thread handle 3008,
query id 117 localhost msandbox update
replace into ix(a,b) values(8,10)
------- TRX HAS BEEN WAITING 3 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 24 page no 4 n bits 80
index idx_a of table `test`.`ix` trx id 1873
lock_mode X locks gap before rec insert intention waiting
---TRANSACTION 1872, ACTIVE 69 sec
4 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2
分析
テーブルにa = 8のレコードがないため、ix(a、b)values(8,10)に挿入するのと似ています。ただし、a = 8はsess1が保持するギャップロック[5-15]と競合するため、ロックモードXがギャップをロックするのを待ってから、再挿入の意図を待機し、待機キューに入ります。このロックはsess1によって保持されます。
replace into ix(a、b)values(9,12);を実行します。トランザクションログは次のようにステートメントを実行します。sess2はすぐにデッドロックを報告します。
*** (1) TRANSACTION:
TRANSACTION 1866, ACTIVE 8 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 1155, OS thread handle 3008, query id 101 localhost msandbox update
replace into ix(a,b) values(8,10)
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 24 page no 4 n bits 80 index idx_a of table `test`.`ix` trx id 1866
lock_mode X locks gap before rec insert intention waiting
*** (2) TRANSACTION:
TRANSACTION 1865, ACTIVE 19 sec inserting
mysql tables in use 1, locked 1
5 lock struct(s), heap size 1136, 5 row lock(s),
undo log entries 3
MySQL thread id 1156, OS thread handle 0672,
query id 102 localhost msandbox update
replace into ix(a,b) values(9,12)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 24 page no 4 n bits 80 index idx_a of table `test`.`ix` trx id 1865 lock_mode X
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 24 page no 4 n bits 80
index idx_a of table `test`.`ix` trx id 1865
lock_mode X locks gap before rec insert intention waiting
*** WE ROLL BACK TRANSACTION (1)
ログ分析
ix(a、b)に置換values(9,12); insert(8,10)と同様に、rec insert意図待機の前にlock_mode Xロックギャップを申請し、ロック要求キューに入って待機する必要があります。
トランザクションT2はix(a、b)values(5,8)に置き換えられます。このステートメントは、4つのロックと1つのIXロック、a = 5行の場合は1行のロック、1-5,5-の2つのaを保持します。 15の間のギャップロック。
トランザクションT1はix(a、b)値に置き換えられます(8,10); a = 8はsess1によって保持されているギャップロック[5,15]と競合するため、ロックモードXがギャップをロックするのを待ってから、再挿入の意図を待機し、待機キューに入ります。内部。
トランザクションT2はix(a、b)値(9,12)に置き換えられ、a = 9も[5-15]の間にあり、T1の挿入意図ロックが解放されるのを待つ必要があります。T1はT2(SQL1)、T2(SQL2)を待ちます。 T1がデッドロックにつながるのを待って、システムはトランザクションT1をロールバックすることを選択します。
4、まとめ
問題の分析と位置付け、解決方法は?開発に関する現在のアドバイスは、replace intoメソッドの使用を避けるか、単一のselect check + insertメソッドを使用するか、特定のデッドロックを受け入れることができる場合は、同時実行を減らしてシリアルに変更することです。興味のある友達は自分自身を再現し、より良い解決策を持ち、お互いにコミュニケーションをとることができます。
5、参照
[1] https://dev.mysql.com/doc/refman/5.7/en/innodb-locks-set.htmlには、さまざまなステートメントをロックする方法が説明されています。デッドロックに関心のある学生は、それを見逃してはなりません。
[2] http://mysqllover.com/?p=1312
この記事は、Yang Qilong先生の公開アカウント(yangyidba)から転送されました。彼は、データベーステクノロジーとパフォーマンスの最適化、障害ケース分析、データベースの運用と保守の技術的知識の共有、個人の成長と自己管理、およびその他のトピックに長い間焦点を当ててきました。
拡張読書
全文は終わりました。
MySQLをお楽しみください:)
Zhishutangの新しいコースK8Sがオンラインになりました
コードをスキャンして、新しい学習の旅を始めましょう