オンラインの問題によって引き起こされる Mysql ロック メカニズムの分析 | JD Logistics Technical Team

背景

最近、ダブルイレブン期間中にグループ内でMysqlのデッドロックによるオンライン障害が発生しましたが、その際、アクティブなデータベース接続数が急増し、アプリケーション層のデータベース接続プールが満杯になっていることがモニタリングで確認できました。を取得できなかったため、後続のリクエストはすべてブロックされました。接続に失敗しました。

全体的なビジネス コードの合理化ロジックは次のとおりです。

@Transaction
public void service(Integer id) {
    delete(id);
    insert(id);
}


データベースインスタンスの監視:

上流の問題を分析し、当時のトラフィック制限の問題を解決した後、問題の根本原因を再分析する時間ができたので、次のように要約します: この記事では、まず、ミューテックス ロックを含む、Mysql のさまざまなロックを分析します。 . 、ギャップ ロック、挿入意図ロックなど、さまざまなロックの使用シナリオを誰もが理解できるようにし、これに基づいてこの問題を分析してください。今後同様のシナリオに遭遇したときに、誰もがすぐに問題を特定できることを願っています。

MySQLのロック機構

Mysql で同じ行のレコードを同時に書き込む問題を解決するために、行ロック メカニズムが導入されました。複数のトランザクションが同時にデータ行を変更することはできません。データベース内のデータ行を変更する必要がある場合の場合、データの行が最初に判断されます。ロックするかどうか。そうでない場合、現在のトランザクションは正常にロックされ、その後の変更操作を実行できます。ただし、行データが他のトランザクションによってロックされている場合、現在のトランザクションはロックされません。ロックされたトランザクションがロックを解放するのを待った後にのみロックできます。成功しました。変更操作を続行します。

この記事のすべての実験で使用されるテーブル作成ステートメントは次のとおりです。

create table `test` (
    `id` int(11) NOT NULL,
    `num` int(11) NOT NULL,
    PRIMARY KEY (`id`),
    KEY `num` (`num`)
) ENGINE = InnoDB;

insert into
    test
values
(10, 10),
(20, 20),
(30, 30),
(40, 40),
(50, 50);




共有ロックと排他的ロック

shared(S) ロックは共有ロックを表します。トランザクションが行の S ロックを保持すると、その行のデータを読み取ることができます。共有モードでステートメント select... from test lock を使用して共有ロックを追加できます。 ** 一般的にはあまり使用されず、**詳しくは説明しません

exclusive(X) ロックは相互排他ロックを表します。トランザクションがデータの行を更新または削除するときは、まずレコードの X ロックを取得する必要があります。別のトランザクションがレコードの X ロックを取得している場合、現在のトランザクションはをブロックし、前のトランザクションが対応するレコードの X ロックを解放するまで待ちます。

S ロックは相互に排他的ではありません。複数のトランザクションは、レコードの S ロックを同時に取得できます。X ロックは相互に排他的です。複数のトランザクションは、同じレコードの X ロックを同時に取得できません。S ロックと X ロックは、相互に排他的です。複数のトランザクションが同じレコードの S ロックと X ロックを同時に取得することはできません。

複数のトランザクションがインデックス上の同じレコードを同時に更新する場合、それらはすべて最初にレコードの X ロックを取得する必要があります。いわゆるロックとは、現在のトランザクション情報を記録するためにメモリ内にデータ構造が生成されることを意味します。ロックの種類と情報待ちかどうか以下の図では、T1とT2がid=30の行レコードを同時に更新し、T1がロックの取得に成功し、メモリ上に生成されたロック構造情報のis_watingフィールドがfalseとなり、その後のトランザクションのロジックが動作します。 T2 ロックの取得が失敗した場合、生成されたロック構造情報フィールド is_wating は true になり、ブロックは T1 のロックが解放されるのを待ちます。

Mysql ログ内のミューテックス ロックのロック情報は次のとおりです。lock_mode X は、rec をロックしますが、ギャップはロックしません。

RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10078 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 8000000a; asc     ;;
 1: len 6; hex 00000000274f; asc     'O;;
 2: len 7; hex b60000019d0110; asc        ;;




ギャップロック

排他的ロックは前のセクションで紹介されました。このロックは、複数のトランザクションがレコードの行を同時に更新するのを防ぐことができますが、ファントム読み取りの問題を解決することはできません。いわゆるファントム読み取りとは、トランザクションが同じクエリを実行するときに、範囲が前後 2 回、最後のクエリで、前のクエリでは使用できなかったレコードが見つかりました。

 セッションA セッションB
T1 更新用に num > 10 かつ num < 15 の場合、test から num を選択します。(0行) 
T2  テスト値(12, 12)に挿入します。
T3 更新用に num > 10 かつ num < 15 の場合、test から num を選択します。(1行) 

上記のシナリオでは、セッション A が T1 と T3 で 2 つの範囲クエリを実行し、セッション B が T2 で範囲内のデータを挿入しました。セッション A が、T3 でセッション B によって挿入されたデータをクエリできた場合、これはファントム読み取りが行われたことを意味します。発生した。現時点では、ミューテックス ロックを使用するだけではファントム読み取りを解決できません。これは、num = 12 のレコードがまだデータベースに存在せず、時間 T2 でのセッション B の挿入を防ぐためにミューテックス ロックを追加することができないためです。

したがって、ファントム読み取り問題を解決するには、新しいロック機構、つまりギャップ ロックを導入するしかありません。ギャップ ロックはミューテックス ロックとは異なります。ミューテックス ロックはレコードの特定の行のみをロックする行ロックですが、ギャップ ロックはレコードの 2 行の間のギャップをロックして、他のトランザクションがこのギャップに新しいレコードを挿入するのを防ぎます。

ギャップ ロックが導入された後、セッション A は時刻 T1 に ID = 20 のレコードに対してギャップ ロックを生成します。その後、セッション B が時刻 T2 にレコードを挿入する場合、最初にギャップ ロックがあるかどうかを確認する必要があります。挿入される次のレコード。明らかに、ギャップ ロックはこの時点で id = 20 のレコードにすでに存在するため、セッション B は id = 20 のレコードに対して挿入意図ロックを生成し、ロック待機状態に入る必要があります。

Mysqlにおけるギャップロックのロックログ情報は以下の通りです: lock_mode X locks gap before rec

RECORD LOCKS space id 133 page no 3 n bits 80 index PRIMARY of table `test`.`test` trx id 38849 lock_mode X locks gap before rec
Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 4; hex 8000001e; asc     30 ;;
 1: len 6; hex 00000000969c; asc       ;;
 2: len 7; hex a60000011a0128; asc       (;;
 3: len 4; hex 8000001e; asc     ;;


ギャップ ロックはファントム読み取りの問題を解決しますが、毎回ギャップをロックするため、データベース全体の同時実行性が大幅に低下します。また、ギャップ ロックとギャップ ロックは相互に排他的ではないため、異なるトランザクションが同時に同じギャップにロックを追加する可能性があります。さまざまなデッドロックの原因となることが多いギャップ ロック

ネクストキーロック

Next-Key Locks は、(Shard/Exclusive Locks + Gap Locks) の組み合わせです。セッション A が相互に排他的な Next-Key Locks をレコード R の行に追加すると、レコード R の X ロックとギャップ ロックを所有することと同じになります。

上記のギャップ ロックの例では、トランザクション 1 はネクスト キー ロックを追加します。つまり、ID = 20 のレコードに X ロックとギャップ ロックが同時に追加されます。

反復可能な読み取り分離レベルでは、更新および削除操作によりデフォルトでレコードに Next-Key Locks が追加されます。Mysql の Next-Key Locks のロック ログ情報は次のとおりです: lock_mode X

RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10080 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 8000000a; asc     ;;
 1: len 6; hex 00000000274f; asc     'O;;
 2: len 7; hex b60000019d0110; asc        ;


インテンションロックの挿入

挿入意図ロックも一種のギャップ ロックであり、行データが挿入される前に INSERT 操作によって取得されます。

レコードを挿入する前に、まず B+ ツリーでレコードの保存場所を見つけてから、挿入する場所の次のレコードにギャップ ロックが追加されているかどうかを確認する必要があります。次のレコードにギャップ ロックがある場合は、挿入操作にはブロックが必要であり、ギャップ ロックを所有するトランザクションがコミットされるまで待機します。同時に、挿入操作を待機しているトランザクションもメモリ内にロック構造を生成し、トランザクションが新しいレコードを挿入しようとしていることを示します。一定のギャップがありますが、現在ブロック状態にあります。生成されたロック構造は、意図的なロックを挿入することです。

実験シミュレーションは次のとおりです。

 セッション1 セッション2 セッション3
T1 始める;  
T2 select * from test where id = 25 を更新用に選択します。  
T3  テスト値(26, 26)に挿入します。(ブロックされました) 
T4   テスト値(26, 26)に挿入します。(ブロックされました)

ステートメント select * from test where id = 25 を更新すると、現在のテーブルにレコードが存在しないため、反復読み取り分離レベルでは、ファントム読み取りを避けるために、ギャップ ロックが (20, 30) に追加されます。 』のギャップ。

ロック ログから、セッション 1 がレコード 30 にギャップ ロックを追加したことがわかります ( lock_mode X は、rec の前にギャップをロックします)。

RECORD LOCKS space id 133 page no 3 n bits 80 index PRIMARY of table `test`.`test` trx id 38849 lock_mode X locks gap before rec
Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 4; hex 8000001e; asc     30 ;;
 1: len 6; hex 00000000969c; asc       ;;
 2: len 7; hex a60000011a0128; asc       (;;
 3: len 4; hex 8000001e; asc     ;;




セッション 2 がレコード 26 を挿入するとき、最初に B+ ツリー内で挿入される位置を特定し、次に挿入位置のギャップにギャップ ロックがあるかどうか、つまり次のレコードにギャップ ロックがあるかどうかを判断します。挿入される場所の id = 30 挿入意図のロックを生成する必要がある場合は、このレコードで待機します。

RECORD LOCKS space id 133 page no 3 n bits 80 index PRIMARY of table `test`.`test` trx id 38850 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 4; hex 8000001e; asc    30 ;;
 1: len 6; hex 00000000969c; asc       ;;
 2: len 7; hex a60000011a0128; asc       (;;
 3: len 4; hex 8000001e; asc     ;;




この時点では、セッション 2 とセッション 3 の両方が、ID = 30 のレコードに挿入意図ロックを追加し、セッション 1 のギャップ ロックが解放されるのを待ちました。生成されたロック レコードは次のとおりです。

オンラインの問題分析

Mysql のさまざまなロック構造を明確に理解したら、以前のオンライン質問に戻って見てみましょう。

@Transaction
public void service(Integer id) {
    delete(id);
    insert(id);
}


上記のビジネス コードには、次の 2 つの状況が存在する可能性があります。

  • 渡されたパラメータ ID は元のデータベースに存在しません
  • 渡されたパラメータ ID は元のデータベースに存在します

今回は主に元のデータベースにidレコードが存在しないかどうかを分析します。

 セッション1 セッション2 セッション3
T1 id = 15 のテストから削除;  
T2  id = 15 のテストから削除; id = 15 のテストから削除;
T3 テスト値(15, 15)に挿入します。  
T4  テスト値(15, 15)に挿入します。 
T5   テスト値(15, 15)に挿入します。

ID = 15 はデータベースに存在しないため、セッション 1 は時刻 T1 でギャップ内の次のレコードにギャップ ロックを追加します。ギャップ ロックは相互に排他的ではないため、セッション 2 とセッション 3 は同時に ID を取得します。時間 T2 = 20 ギャップロック

次の図では、tx: T1、T2、および T3 はそれぞれセッション 1、セッション 2、およびセッション 3 を表します。

T3時点でセッション1にid=15のレコードが挿入されると、挿入位置以降のレコードにGap Lockが存在するかどうかを判定し、存在する場合はそのレコードにInsert Intention Lockを生成してトランザクションを待つ必要があるギャップロックを長押しすると解除されます。

挿入ステートメントがセッション 2 の時刻 T4 で実行されると、挿入位置の後のレコードにギャップ ロックがあるため、挿入意図ロック待機も生成する必要があります。この時点で、デッドロックが形成されていることは明らかです。セッション 1 は挿入目的のロックを生成し、セッション 2 とセッション 3 のギャップ ロックが解放されるのを待ちます。一方、セッション 2 も挿入目的のロックを生成し、ギャップを待ちます。セッション 1 とセッション 3 のロックが解放されます。

T4 でデッドロックが検出された後、Mysql はロールバックするトランザクションの 1 つを選択します。この時点でセッション 2 がロールバックされ、保持されているすべてのロック リソースが解放されると仮定します。セッション 1 は実行を継続できますか? 明らかにそうではありません。セッション 1 はセッション 3 のギャップ ロックが解放されるのをまだ待機しており、ブロックして待機し続けます。

時刻 T5 で、セッション 3 が挿入ステートメントの実行を開始します。このとき、T4 と同時にデッドロックが形成されます。セッション 1 によって生成された挿入意図ロックは、セッション 3 のギャップ ロックが解放されるのを待っています。セッション 3 で生成された挿入意図ロックは、セッション 1 を待機しています。ギャップ ロックが解放されます。この時点で、セッション 3 はロールバックしてすべてのロック リソースを解放し、最終的にセッション 1 が正常に実行できるようになります。

3つの同時スレッドのデッドロック解析が完了すると、デッドロックはあるもののデッドロック検出ですぐに検出でき、プログラムは正常に実行できるのではないかと思う人もいるかもしれませんが、これは一体何が問題なのでしょうか?実際、上記の問題は同時実行量が少ないことと、デッドロックの検出が早いことが主な理由ですが、同時実行量が100倍、さらには1000倍に拡張されても問題は発生しないのでしょうか?

オンラインの問題が発生したときのインターフェイスへの呼び出し数を見てください。

さらに、ローカルで 300 スレッドの同時実行をシミュレートします。すべてのトランザクションの同時実行を分析するのは人間の脳にとって非常に複雑なので、今回はトランザクション 1 のみを分析ポイントとして使用します。

図からわかるように、T1 が挿入文を実行する場合、T2 ~ T101 に保持されているギャップ ロックが解除されるまで待つ必要があり、その後、T2 ~ T6 が同時に挿入文を実行する可能性があります。デッドロックの検出とトランザクションのロールバックを実行します。後続のトランザクションが挿入文を実行している限り、デッドロックのロールバックが実行され、正常に動作するように見えますが、デッドロックの検出処理中に新しいトランザクション (T101 ~ T200) が発生します。ギャップ ロックを取得し、ロックがキュー内のトランザクションを待機するようになります。ロック待機キューに多くのトランザクションがある場合、**Mysql の全体的なデッドロック検出時間の複雑さは O(n^2)** になります。ロックを待機している新しいトランザクションがあるたびに、デッドロック検出では、ロック待機キュー内でその前に待機しているトランザクションをトラバースして、自身の追加によってループが形成されるかどうかを判断する必要があります。このとき、検出には 1 つのトランザクションが消費されます。 CPU リソースが大量に消費されるため、データベースの全体的なパフォーマンスが低下し、デッドロックの検出時間が増加し、MySQL のアクティブな接続数が大幅に増加し、ロック待機のために接続が解放されず、最終的にはアプリケーション層に障害が発生します。接続プールがいっぱいになります。

上記の分析に基づくと、この問題の主な理由は、短期間に同じデータ行を最初に削除してから挿入するという大量の同時リクエストが存在することです(最初に更新してから挿入する場合も同様です)。デッドロック待機とアプリケーション層接続で、プールがいっぱいになり、多数のアップストリーム要求がタイムアウトして再試行され、さらにロック待機が発生し、最終的にはデータベースに依存するすべてのビジネスに影響を与えました。

したがって、今後同様のロジックがビジネス コードに存在する場合は、短期間に同じデータ行を更新して挿入するという同時操作を避けるために重複防止を行う必要があります。同時に、反復可能な読み取り分離カテゴリでは、更新および削除操作に対して Next-Key ロックがデフォルトで追加されます。ギャップ ロックの導入により、同時実行状況でデッドロックの問題が発生しやすくなります。これも考慮する必要がある問題です。ビジネスロジックの実装において。

要約する

この記事では、オンラインの問題を背景として Mysql のさまざまなロック メカニズムを詳細にまとめ、ロックのタイミングと各ロックの具体的な使用シナリオを分析します。ギャップ ロックの使用には特に注意が必要です。ロックは相互に排他的ではないため、複数のトランザクションが同時に実行されるとデッドロックが簡単に発生する可能性があります。

著者: JD Logistics Zhang Gongyan

出典:JD Cloud Developer Community Ziyuanqishuo Tech 転載の際は出典を明記してください

Alibaba Cloudが深刻な障害に見舞われ、全製品が影響(復旧) Tumblr がロシアのオペレーティングシステムAurora OS 5.0 を冷却新しいUIが公開 Delphi 12とC++ Builder 12、RAD Studio 12多くのインターネット企業がHongmengプログラマーを緊急採用UNIX時間17 億時代に突入しようとしている (すでに突入している) Meituan が兵力を募集し、Hongmeng システム アプリの開発を計画Amazon が Linux 上の .NET 8 への Android の依存を取り除くために Linux ベースのオペレーティング システムを開発独立した規模はFFmpeg 6.1「Heaviside」がリリースされまし
{{名前}}
{{名前}}

おすすめ

転載: my.oschina.net/u/4090830/blog/10143236