明らかに 1 行のステートメントが変更されただけですが、なぜこれほど多くのロックがあるのでしょうか?

この問題については主に次の 3 つの側面から議論します。

  • いつ追加されますか?

  • 追加方法は?

  • いつ追加すべきであり、いつ追加すべきでしょうか?

01 いつ追加されますか?

写真

1.1 ディスプレイロック

MySQL のロックは、明示的ロックと暗黙的ロックに分けることができます。明示的ロックは SQL に直接反映されることが多いため、識別が容易です。一般的な明示的ロック ステートメントには、主に次のようなものがあります。

▶︎ select ... for update;▶︎ select ... in share mode;

2 つの違いは、前者は排他ロックを追加し、後者は共有ロックを追加することです。排他ロックを追加すると、その後のデータ範囲に対する書き込みおよび読み取り操作がブロックされます。別の共有ロックは読み取りをブロックするのではなく、書き込みをブロックしますが、これにより、電子商取引シナリオなど、いくつかの問題が発生することがよくあります。在庫を更新するとき、一貫したデータ更新を保証するために、最初に商品データをロックする必要があることがよくありますが、このときに 2 つのスレッドが同時に在庫を更新すると、データ更新異常が発生する可能性があります。

そのため、ビジネスではデータをロックするために更新に select... を使用することがよくあります。次のような、あまり一般的には使用されないロック方法もいくつかあります。

  • グローバル ロック: 読み取りロックを使用してテーブルをフラッシュし、主に論理バックアップを実行するときに使用されます。

  • テーブル锁:ロックテーブル … 読み取り/書き込み

1.2 暗黙的なロック

暗黙的なロックは特に注意が必要です。多くの「落とし穴」は暗黙的なロックの存在によって引き起こされます。多くの場合、目に見えないロックが最も致命的です。

テーブル ロックに加えて、テーブル レベルのロックにはメタデータ ロックも含まれます。

▶︎ 追加、削除、変更、クエリを実行すると、MDL 読み取りロックが追加されます。
▶︎ テーブル構造の変更が行われると、MDL 書き込みロックが追加されます。

これによってもたらされる問題は、テーブルにインデックスを追加したり、テーブル構造を変更したりするときに、MDL 書き込みロックが原因で、通常のオンライン読み取りおよび書き込みリクエストがブロックされてしまうことです。このとき、アップストリームの失敗再試行メカニズムが失敗する可能性があります。がトリガーされると、雪崩のようにリクエストが発生し、DB がハングアップする可能性があります。

もう 1 つは、日常業務に密接に関係する行ロックとギャップ ロックです。追加、削除、または変更するときに、現在の分離レベルに応じて行ロックまたはギャップ ロックを追加します。このとき、次のことが必要です。パフォーマンスのほかに、ロック範囲が大きすぎてリクエストがブロックされ、アップストリームの再試行がトリガーされ、サービス雪崩や DB ハングが発生する可能性があるというリスクもあります。

1.3 ロックされるのか?

写真

これについて言えば、学生の中には質問があるかもしれません。追加、削除、変更がロックされていると、読み取り時のパフォーマンスが非常に低下します。特に、読み取りと書き込みが多いビジネス シナリオでは、読み取りリクエストが来ると、DBあなたは私に毎分チェックされませんでしたか?実際、ここでの innodb エンジンは mvcc テクノロジー、つまりマルチバージョン同時実行制御を使用しており、原則として、データが更新される間、更新されたトランザクション ID と対応するデータをアンドゥログに記録し、Readview のアクティブなトランザクション ID を維持します。 , そのため、トランザクションを実行する際に、どのデータが見えて、どのデータが見えないのかが簡単にわかります。このとき、データの読み込みは当然ロックの影響を受けず、正常に読み込むことができます。

02 追加方法

写真

ここでのロックの追加方法に関する説明は、実際には、ロックのタイプと範囲、つまりどのロックが使用され、どこに追加されるのかを理解することです。この問題について説明する前に、トランザクション分離レベルを見てみましょう。

▶︎ コミットされていない読み取り;
▶︎ コミットされた読み取り;
▶︎ 反復可能な読み取り;
▶︎ シリアル

なんでそんなこというの?分離レベルはロックにも影響するため、コミットされた読み取りはダーティ読み取りの問題を解決しますが、ファントム読み取りの問題は解決しません。リピータブル読み取りはギャップ ロックを導入することでファントム読み取りの問題を解決します。これは、異なる分離レベルが使用されることを意味します。ロックは同じではありませんが、明らかなことが 1 つあります。それは、分離レベルが高いほど、ロックの使用がより厳密になるということです。反復読み取りはデフォルトのトランザクション分離レベルですが、オンラインで設定された分離レベルは多くの場合読み取りコミットされます。これは主に、このレベルで十分であり、同時実行パフォーマンスが向上するためです。次に説明する範囲は、主に Read Committed (RC) とRepeatable Read (RR) です。

対応するルールに基づいた詳細な分析は次のとおりです。

▶︎ 原則 1: ロックの基本単位はネクストキーロックです。ネクストキーロックは前開きと後閉じの間隔であることをまだ覚えておいていただければ幸いです。
▶︎ 原則 2: 検索プロセス中にアクセスされたオブジェクトのみがロックされます。
▶︎ 最適化 1: インデックスに対する同等のクエリの場合、一意のインデックスをロックすると、ネクストキー ロックは行ロックに縮退します。
▶︎ 最適化 2: 右にトラバースし、最後の値がインデックス上の等価クエリの等価条件を満たさない場合、ネクストキー ロックはギャップ ロックに縮退します。
▶︎ バグ: 一意のインデックスに対する範囲クエリは、条件を満たさない最初の値にアクセスします。

他に注意すべき点が 2 つあります。

▶︎ ロックがインデックスに追加されます。
▶︎ ギャップ ロックは排他的ではなく共有されます。

2.1 ラジコン

次に、これらについて個別に説明します。少し長くなり、読むには忍耐が必要になる場合があります。

1 つ目は RC レベルです。このレベルのロック ルールは行ロックのみを必要とするため、比較的単純です。まず、テーブルを設計します。

CREATE TABLE `t_db_lock` (  `id` int(11) NOT NULL,  `a` int(11) DEFAULT NULL,  `b` int(11) DEFAULT NULL,  PRIMARY KEY (`id`),  KEY `a` (`a`)) ENGINE=InnoDB;
insert into t_db_lock values(0,0,0),(5,5,5),(10,10,10);
2.2 主要工事相当物の存在

写真

▶︎ この時点で sessionA が主キーのデータを更新し、現在のレコードの主キー値を 1 に更新していることがわかります。このとき、データベースは id=1 に行ロックを追加し、 0、つまりこの時点では、この ID の更新はブロックされます;
▶︎ したがって、sessionB が id=1 のレコードを挿入しようとすると、ブロックされます;
▶︎ ただし、sessionC が更新するので id=5 のレコードなので正常に実行できます。

2.3 非固有の同等物

写真

▶︎ sessionA は通常のインデックスの判定条件に従ってデータを更新しますが、インデックスに行ロックが付加されているため、この時点でカラム a に関するインデックスデータがロックされます;
▶︎ しかし、なぜこの時点で id=0 のデータの更新もブロックされているのでしょうか?このとき、aにインデックスを追加するだけでなく、テーブルの更新操作も行われるため、このときアクセスされる主キーのインデックスもロックされます。同じ行なので、このときの更新はalso block;
▶︎ 同様に、b=0のデータを更新すると、それに対応する主キーインデックスも同じデータとなるため、この時点でも更新はブロックされますが、ただし、この時点で b=5 のデータを更新すると、このデータが正常に更新された場合。

2.4 主キーに相当するキーが存在しない

写真

▶︎ sessionA は ID 2 のロックを追加しました。この時点では、行レコードは存在せず、行ロックは正常に追加されなかったため、他のセッション リクエストはブロックされません。
▶︎ sessionB 実行に成功しました。
▶︎ sessionC は正常に実行されました。

2.5 インデックスのない同等のものは存在しない

写真

▶︎ この状況は主キーやその他の値と矛盾していますが、対応するロックレコードが見つからないため、以降の更新操作は正常に実行できます。

2.6 主キーの範囲

写真

▶︎sessionA は範囲に従ってロックされ、id=0 と 5 の 2 行のデータがロックされます。
▶︎sessionB は行のデータの更新によりロックされますロックされている id=0 のブロック Block;
▶︎ sessionC id=1 のレコードは存在しないので普通に挿入できます。このとき、セッション A で id >= 0 および id <= 5 の select * from t_db_lock を実行すると、データがもう 1 つ見つかります。

2.7RR

ここでの反復可能な読み取りレベルでは、主にギャップ ロックのロック シナリオについて説明します。このロック状況は、コミットされた読み取り分離レベルよりもはるかに複雑になります。セッション トランザクション分離レベルの反復可能読み取りを設定します。

2.8 主キーに相当するものが存在する

写真

▶︎sessionA は既存の行 id=5 をロックします。ロック規則によれば、一意のインデックスは行ロックに縮退するため、行 id=5 のみをロックします。実際、これはすでにunique Index. の場合、ファントム読み取りは行われないため、ファントム読み取りは行が存在するかどうかのみに依存するため、再度書き込まれないようにするために行をロックするだけで済みます。

▶︎ sessionB と sessionC の両方がロック範囲内にない場合、挿入は成功します。

2.9 一意でない同等物

写真

▶︎SessionA は既存のレコード a=5 をロックします。一意ではないインデックスであるため、ロック ルールに従って、まず a インデックスとネクスト キー ロック (0,5] をスキャンし、次に最初のレコードまで右にトラバースします)。条件が満たされない場合 (ルール 5 に従って、一意のインデックスの範囲クエリは条件を満たさない最初の値にアクセスします)、ギャップ ロックに縮退するため、ロック範囲は (5,10) になります。全体のロック範囲は (0,10) で、更新の場合、主キーのインデックス範囲 (0,10) 内にもロックが追加されます。

▶︎ sessionB は主キーインデックスのロック範囲内にあるため、ブロックされます。

▶︎ sessionC はこの時点では通常インデックスと主キーインデックスの範囲内にないため、実行は成功します。

ここで、非一意の等値クエリの場合、ロック範囲が主キーの等価値よりも大きいため、非一意のインデックスをロックする場合はこの範囲に注意する必要があることがわかります。

2.10 主キーに相当するものが存在しない

写真

▶︎SessionA この時、id=3のレコードに行ロックがかかりますが、この時点では行3のレコードが存在しないため、この範囲はロックされます ロック原理により、右にトラバースします最後の値が満たされないなど。値の条件、ネクストキーロックはギャップロックに縮退し、このときのロック範囲は(0,5)です。

▶︎sessionB はロック範囲内にあるためブロックされます。

▶︎sessionC はロック範囲内にないため、ロックに成功します。

なぜここに範囲ロックが追加されているのでしょうか? 実際、主な解決策はファントム読み取り問題です。この範囲にロックがないと仮定すると、T1 時に sessionB が正常に実行され、id = 3 の select * from t_db_lock が実行されます。 T2 時にもう一度実行すると、以前はクエリされなかった結果が、まるで錯覚のようにクエリできるようになったことがわかります。このファントム読み取りを回避するには、この範囲にロックする必要があります。

2.11 一意でない同等なものは存在しない

写真

▶︎SessionAは行a=3をロックしますが、この行はdbに存在しないのでネクストキーロックも追加され、ロックはすべてインデックスに追加されるため、(0,5)範囲ロックがかかります。しかしここで不思議な現象があり、a=5の場合、id<5ならブロック、id>5なら成功するという結果から、このときaのロックは偏っていて厳密ではないようです。 a=5 の場合、対応する挿入レコードはロックされます。

2.12 主キーの範囲

写真

▶︎SessionA は範囲クエリ ロックを実行します。これは、更新のための select * from t_db_lock where id = 5 と意味的に同等ですが、実際のロック状況は依然として大きく異なります。まず、id >= 5 が同等のクエリに従ってクエリされます。行=5 は (0,5] としてロックされます。これは一意のインデックスであるため、行ロックに縮退します。そのため、行 id=5 にロックが追加されます。次に、右側にクエリを実行して、一致する最初の値を見つけます。条件を満たしません。つまり、行 id=10 に対して、next-key lock(5,10] を追加します。これは同等のクエリではないため、ギャップ ロックへの縮退はなく、全体のロック範囲は [ 5,10];

▶︎sessionB はロック範囲内にないため、挿入は成功します。

▶︎sessionC はロックされており、挿入に失敗します。主キーの競合が報告されるのではなく、ブロックされることに注意してください。

2.13 非固有のスコープ

写真

▶︎ sessionA のロック範囲は主キーインデックスと異なり、(0, 5] の範囲の行ロックに縮退しないため、全体のロック範囲は (0, 10])

2.14 インデックスのない同等のものは存在しない

写真

▶︎ セッションAのロックされているレコードは行b=6です。bはインデックスを作成していないため、bインデックス上のすべてのレコードがロックされます。更新ロックされているため、メインテーブルで更新されたものとみなされます。メインテーブル 関連レコードもロックされるため、ロック期間中テーブルはロック状態になり、更新操作は成功しません。これはオンラインでの非常に危険な操作であり、データベースが壊れる可能性があります。破壊されました。

03 追加する場合と追加しない場合

上記の分析を通じて、ステートメント内のロックの種類とロックの範囲を一般的に理解する必要があります。単純な SQL はパフォーマンスを低下させる可能性が高いため、悲観的ロックの使用には注意が必要であることがわかります。サービス品質に関して、どのような場合に追加する必要があり、どのような場合に追加しない方がよいでしょうか?

データベースの同時実行シナリオについては、次のように考えることができると思います。

▶︎ 可能な限り楽観的ロックの使用を優先します。

▶︎悲観的ロックを使用する必要がある場合は、ロックされたキーにインデックスを追加する必要があります。

▶︎ DB の分離レベルを確認し、SQL の競合やデッドロックの考えられる原因を分析し、SQL が長時間ブロックされるのを回避します。

実際、db の相互排他ソリューションに特効薬はありません。ソリューションは特定のビジネス シナリオに従って策定する必要があります。しかし、考えられるいくつかの落とし穴を事前に特定し、低レベルのエラーを回避し、最適化する能力を備えています。彼、これは自分自身を改善し続けるための良い方法です。

出典: この記事は公開アカウント Tencent Cloud Developer からの転載です

おすすめ

転載: blog.csdn.net/LinkSLA/article/details/134847811