目次- 1. ロックとラッチ
- 2. 繰り返し読み取り
- 3. インサートロックプロセス
- 3.1 ロックモード
- 3.2 ロック処理
- 3.3 暗黙のロック
- 4. ロックプロセスを選択
この記事の前提: コード MySQL 8.0.13 Repeatable Read の現在の読み取りのみがソートされます。 Read Committed はるかに簡単です。また、スナップショット読み取りはMVCC に基づいており、ロックを必要としないため、この記事では説明しません。 1. ロックとラッチInnoDB のlock は、トランザクションでアクセスまたは変更されたrecord に追加されるロックであり、通常、トランザクションがコミットまたはロールバックされると解除されます。ラッチは、BTree 上のrecord を検索するときに Btree ページに追加されるロックです。通常、ページ内の対応するrecord をロックし、アクセス/変更後に解放します。ラッチのロック範囲は、ロックの範囲よりもはるかに小さくなります。具体的な実装では、次の図に示すように、大きなtransaction がいくつかの小さなmini transaction(mtr ) に分割されます。 insert 、 select…for update 、 update 操作を順番に実行するtransaction があります。これらの 3 つの操作は、それぞれ 3 つの mtr に対応します。各 mtr は次の操作を完了します。
- btree でターゲット
record を見つけて、關page latch を追加します。 - 対象
record lock を追加し、対応するrecord を変更します page latch
を解除するpage latch
なぜこれをするのですか?同時実行性のため、トランザクション内の各操作では、ステップ 2 が完了した後、他の同時トランザクションがそれを変更できないように、対応するrecord がロックによって保護されています。したがって、この時点でrecord が配置されているpage latch を占有する必要はありません。そうしないと、他のトランザクションが同じpage の異なるrecord アクセス/変更するときに (並行して実行できます)、 page latch によってここでスタックされます。 
ロックは在lock_sys->rec_hash 。個record lock rec_hash で<space_id 、 page_no 、 heap_no> によって識別されます。 latch 、 page に対応するbufferpool のblock にあり、 block->lock に対応しています。 この記事ではロック関連のことだけに焦点を当てており、ラッチについては後で別の記事で説明します。 2. 繰り返し読み取り各分離レベルについては詳しく説明しません。ここでは主に RR について説明します。名前が示すように、RR は繰り返し性をサポートします。つまり、トランザクション内で同じSELECT…FOR UPDATE 複数回実行すると、同じ結果セットが表示されます (このトランザクションによる変更を除く)。これには、他のトランザクションが SELECT 間隔内に新しいレコードを挿入できないことが必要です。したがって、条件を満たすレコードをロックすることに加えて、SELECT は対応する間隔をロックして保護する必要があります。 InnoDB の実装では、指定された間隔を一度にロックするロックはありません。代わりに、大きな間隔ロックが分割され、間隔内にすでに存在する複数のレコードに適用されます。そのため、ギャップ ロックと次のキー ロックの概念が導入され、特定のレコードに追加されます。 -
Gap lock このレコードと前のレコードの間の空き区間を保護します。 -
Next-key lock このレコードと前のレコード間の左開きと右閉じの間隔を保護します。
これらはすべて、この間隔が他のトランザクションによって新しいレコードに挿入されるのを防ぎ、RR を実装するためのものです。 次に、ソースコードの実装から Insert と Select がどのようにロックされているかを見てみましょう。これらを組み合わせることで、InnoDB の RR がどのように実装されているかを理解できます。挿入ロックは挿入操作全体と複数の関連関数に分散されますが、選択ロックは在row_search_mvcc に集中しています。 3. インサートロックプロセス3.1 ロックモードロックモードは主に共有(S)と排他(X)です(コード内のLOCK_SとLOCK_Xに相当)。 ギャップロックのモードには、主にレコードロック、ギャップロック、次キーロック(コード内のLOCK_REC_NOT_GAP、LOCK_GAP、LOCK_ORDINARYに対応)が含まれます。 実際の使用では、ロックの実際のタイプは、after mode|gap_modeです。レコードロックは、単一のレコードに適用されるレコードロックです。 Gap lock/Next-key lock も特定のレコードに適用されますが、これは、他の同時トランザクションがレコードの前のギャップに挿入されないようにするために使用されます。これはどのように実装されていますか?InnoDBは、実際のタイプが (LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION)
Gap lock/Next-key lock とは相互に排他的です。挿入前に挿入位置の次のレコードでロックが検出されると、次のレコードに挿入意図ロックが追加され、トランザクションがギャップに新しいレコードを挿入する意図があることを示します。ここで別のトランザクションがすでにGap/Next-key lock を設定している場合は、この場所を保護したいため、現在の挿入意図ロックは関連するトランザクションがコミットされるまで待機する必要があります。この検出は一方向のみです。つまり、挿入意図Gap/Next-key lock が、どのロックも挿入意図ロックの解除を待つ必要はありません。そうしないと、このギャップ内の競合しない挿入操作の同時実行に重大な影響が出ます。 具体的なロック競合検出はlock_rec_has_to_wait関数で行われます。一般的な原則は、2つのロックが互換性があるか互換性がないかを判断するために、まずモード競合検出を実行することです。

競合がない場合、ロックに互換性があり、待機する必要がないことを意味します。競合がある場合は、ギャップ モード競合例外検出が実行されます。これは次のように要約されます。 
ギャップ モードが競合しない場合は、例外としてロックは互換性があると見なされ、待機する必要はありません。以下が見られます: - 意図ロックを挿入するには
Gap lock とNext-key lock を待つ必要があります - いかなるロックも意図ロックの挿入を待つ必要はありません
Gap lock ロックを待つ必要がないNext-key lock 他のNext-key lock及Record Lock を待つ必要があり、その逆も同様です。
ロック互換性の原則を理解したので、実際の挿入プロセスでそれらをどのように使用するかがわかります。 3.2 ロック処理Insert の順序は、最初に主キー インデックスを挿入し、次にセカンダリ インデックスを順番に挿入します。以下は、コードから整理されたプロセス、 entry を挿入する操作です。
主キーインデックスの場合: (1)まずBtreeを検索し、関連するpage latch を追加し、 record (<= entry) を見つけます。 (2)挿入するエントリがすでに存在する場合、つまりentry = record 場合、次のように判断される。 -
INSERT ON DUPLICATE KEY UPDATE の場合は、 record にX Next-key lock 追加します。 - 通常の
INSERT の場合は、 record にS Next-key lock を追加します。
次に、レコードが削除マークであるかどうかを判断します。 - 削除マークでない場合は、重複が存在することを意味し、
DB_DUPLICATE_KEY 上位層に返されます。上位層は、 INSERT ON DUPLICATE KEY UPDATE か通常の INSERT かに基づいて、操作を更新に変換するか、ユーザーに重複エラーを報告するかを決定します。 - 削除マークの場合は、
duplicate record が存在しないことを意味しますので、下に進みます
(3)レコードの次のレコードにロックがあるかどうかを判断します。ロックがある場合は、挿入意図ロックを追加して、エントリが挿入される間隔に他のGap lock/Next-key lock 保護がないことを確認します。 (4)エントリを挿入 (5) page latch を解除する。ページラッチはまだロックされている。 セカンダリインデックスの場合 (1)まずBtreeを検索し、関連するページラッチを追加し、 record (<= entry) を見つけます。 (2)挿入するエントリがすでに存在する場合、つまりentry = record あり、現在のインデックスが一意である場合: -
INSERT ON DUPLICATE KEY UPDATE の場合は、 record にX Next-key lock 追加します。 - 通常のINSERTの場合は、
record2 にS Next-key lock を追加します。
レコードとエントリが等しいかどうかを判断します。 それらが等しく、通常の INSERT である場合は、レコードが削除マークであるかどうかを判断します。 - 削除マークでない場合は、重複が存在することを意味し、
DB_DUPLICATE_KEY 上位層に返されます。上位層は、 INSERT ON DUPLICATE KEY UPDATE還 通常の INSERT かに基づいて、操作を更新に変換するか、ユーザーに重複エラーを報告するかを決定します。 - 削除マークの場合は、実際には重複はないので、下に進みます
(3) INSERT ON DUPLICATE KEY UPDATE であり、現在のインデックスが一意である場合、同じエントリが他のトランザクションによって挿入されるのを防ぐために、次のトランザクションに対してrecord X Gap lock 設定されます。 (4)レコードの次のレコードにロックがあるかどうかを確認します。ロックがある場合は、挿入意図ロックを追加します。 (LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION)
エントリを挿入する範囲が他のGap lock/Next-key lock (5)エントリを挿入 (6)ページラッチを解除する 注: [セカンダリ インデックス] のステップ 3 は冗長に見えます。他の同時トランザクションがINSERT ON DUPLICATE KEY UPDATE 使用して同じレコードを挿入したとしても、ステップ 1 は [プライマリ キー インデックス] プロセスと同様にシリアルにしか入力できないためです。最初のスレッドはエントリと同じレコードを見つけられないため、ステップ 4 に進んで挿入します。2 番目のスレッドは、ステップ 6 が終了し、ページ ラッチが解放された後にのみステップ 1 に入ることができます。この時点で、ステップ 2 では、追加されたレコードのX Next-key lock に引っかかってしまい、スレッド 1 のトランザクションがコミットされた後にのみ続行できます。そのため、競合は発生しないと思われますか? 上記のプロセスは row_ins_index_entry 関数内にあり、具体的なエントリは次のとおりです。
mysql_parse->mysql_execute_command->Sql_cmd_dml::execute->
Sql_cmd_insert_values::execute_inner->write_record->ハンドラ::ha_write_row->
ha_innobase::write_row->row_insert_for_mysql->row_insert_for_mysql_using_ins_graph->
行挿入ステップ->行挿入->行挿入インデックスエントリステップ->行挿入インデックスエントリ
挿入意図ロックは lock_rec_insert_check_and_lock 関数に追加され、エントリは次のようになります。
行インインデックスエントリ->行インクラストインデックスエントリ/行インセックインデックスエントリ->
btr_cur_optimistic_insert/btr_cur_pessimistic_insert->btr_cur_ins_lock_and_undo->
ロック_rec_insert_check_and_lock
3.3 暗黙のロックもう一つのポイントは、挿入操作は明示的にロックされないことです。各挿入レコードにはデフォルトで暗黙のロックがあり、これはレコードの隠しフィールド trx_id を通じて検出されます。主キー インデックスの場合、挿入するレコードが Btree で見つかった場合は、既存のレコードの trx_id を比較するだけで済みます。この trx_id に対応するトランザクションがまだアクティブなトランザクションである場合、このレコードの挿入トランザクションがコミットされていないことを意味します。これは暗黙的に、このレコードにロックがあることを意味します。このとき、明示的なロックに変換され、 lock_sys に配置されて待機します。これは、パフォーマンスを向上させ、lock_sys での操作を最小限に抑えるために行われます。セカンダリ インデックスの暗黙的なロック検出は、プライマリ キー インデックスほど簡単ではありません。セカンダリ インデックス レコードにはtrx_id が記録されないためです。現在アクティブなトランザクション リスト内の最小の trx_id と、配置されているページのmax_trx_id 比較することしかできません。max_trx_id より小さい場合は、このページを変更した最後のトランザクションがコミットされたことを意味し、レコードに暗黙的なロックはありません。max_trx_id 以上の場合は、プライマリ キーに戻って対応するプライマリ キー レコードを見つけ、元に戻す履歴バージョンをトラバースして暗黙的なロックがあるかどうかを確認する必要があります。具体的な実装はrow_vers_impl_x_locked_low にあります。 4. ロックプロセスを選択現在の読み取りを行うための SELECT のロック プロセスは row_search_mvcc にあります。SELECT ステートメントは、この関数に複数回入ります。最初はindex_read->row_search_mvcc で、これは通常、WHERE で正確なレコードを見つけるためのインデックスへの最初のアクセスです。後続の各アクセスはgeneral_fetch->row_search_mvcc で行われ、WHERE 条件を満たすすべてのレコードが取得されるまで、特定の条件に従ってprev/next record がトラバースされます。特定のロックは、レコードにアクセスしてトラバースするプロセス中に実行されます。 row_search_mvcc コードは非常に長いです。ここでは、ロック関連のプロセスのみを要約します。 - インデックス上の search_tuple に対応するレコードを検索します。 (ここでのレコードは、前述のインデックス Btree を介した
search_tuple に対応するレコードの最初の検索である場合もあれば、複数の general_fetch の後に以前に保存されたカーソルを介して最後のアクセス位置を復元することによって取得された前/次のレコードである場合もあります。) - index_readでモードが
PAGE_CUR_L またはPAGE_CUR_LE の場合、検索されたレコードの次のレコードにGAP LOCKを追加します。 - レコードが最小値の場合は、ステップ 9 next_rec にジャンプします。最大値の場合は、Next-key Lock を追加して、ステップ 9 next_rec にジャンプします。
- index_readの場合、レコードがsearch_tupleと等しくない場合は、
record にGAP LOCK を追加し、NOT FOUNDを返します。 - ここでは、レコードが search_tuple と等しいことを示し、レコードに Next-key Lock を追加します。2 つの例外を除いて、Rec Lock のみが追加されます。
- index_readの場合、現在のインデックスが主キーインデックスであり、
mode がPAGE_CUR_GE であり、 search_tuple のフィールド数がインデックス内の一意のフィールド数と等しい場合、 - unique_search であるかどうか、つまり search_tuple のフィールド数が現在のインデックスの一意のフィールド数と等しく、現在のインデックスが主キー インデックスであるか (セカンダリ インデックスであり、search_tuple に NULL フィールドが含まれていない)、レコードが削除マークではないかどうかを確認します。
- これはロックが成功したことを示し、レコードが削除済みとしてマークされているケースを処理します。
- 現在のインデックスは主キーインデックスであり、
unique_search であるため、 NOT FOUND が返されます。 - それ以外の場合は、ステップ9 next_recに進みます。
- 現在のインデックスがセカンダリインデックスであり、プライマリキーインデックスをチェックする必要がある場合は、プライマリキーインデックス内の対応する
primary record を見つけて、 Rec Lock を追加します。プライマリレコードが削除済みとしてマークされている場合、現在のセカンダリインデックスはステップ9 next_recにジャンプします。 - 成功、
DB_SUCCESS を返します - next_rec:モードに応じて対応する
prev/next record を取得し、ステップ3にジャンプして続行します。
ステップ 3 に注目しましょう。通常、レコードが infimum または supremum の場合、ページ上で複数の genera_fetch が実行され、前のレコードまたは次のレコードが取得されてからページの端に到達します。infimum の場合、ロックは追加されず、前の prev レコード (つまり、前のページの supremum) に直接アクセスされます。supremum の場合、現在のページの最後のユーザー レコードと次のページの最初のユーザー レコード間のギャップを保護するためにギャップ ロックが追加されます。 プロセスには他には何もありません。 - 条件を満たすレコードの場合、デフォルトでは次キーロックが追加されます。
- セカンダリ インデックスがテーブルに返されると、プライマリ キーのみが再ロックされます。
- 特別なシナリオでは、一部の
Next-key lock Rec lock にダウングレードされます (手順 5) - ギャップロックのみが追加される特別なシナリオもあります(手順2と4)
要約:
上記は基本的にInnoDB にトランザクション ロックを追加するプロセスです。 Insert とSelect のロック プロセスを組み合わせることで、トランザクション ロックの原理と実装が基本的に明らかになります。 これで、MySQL InnoDB トランザクション ロック ソース コード分析に関するこの記事は終了です。MySQL InnoDB トランザクション ロック ソース コード分析に関するより関連性の高いコンテンツについては、123WORDPRESS.COM の以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。 以下もご興味があるかもしれません:- MySQL Innodbの主な機能挿入バッファ
- MySQL InnoDB ストレージエンジンのメモリ管理の詳細な説明
- MySQL InnoDB ReplicaSet の簡単な紹介
- MySQL Innodb インデックス メカニズムの詳細な紹介
- MySQLのInnoDBストレージエンジンにおけるさまざまなロックの詳細な説明
- MySQL ストレージ エンジン InnoDB と MyISAM
- MySQL の innoDB でファントム リードを解決する方法
|