MySQL InnoDB トランザクション ロック ソースコード分析

MySQL InnoDB トランザクション ロック ソースコード分析

この記事の前提:

コード MySQL 8.0.13

Repeatable Readの現在の読み取りのみがソートされます。 Read Committedはるかに簡単です。また、スナップショット読み取りはMVCCに基づいており、ロックを必要としないため、この記事では説明しません。

1. ロックとラッチ

InnoDBlockは、トランザクションでアクセスまたは変更されたrecordに追加されるロックであり、通常、トランザクションがコミットまたはロールバックされると解除されます。ラッチは、BTree 上のrecordを検索するときに Btree ページに追加されるロックです。通常、ページ内の対応するrecordをロックし、アクセス/変更後に解放します。ラッチのロック範囲は、ロックの範囲よりもはるかに小さくなります。具体的な実装では、次の図に示すように、大きなtransactionがいくつかの小さなmini transaction(mtr ) に分割されます。 insertselect…for updateupdate操作を順番に実行する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_idpage_noheap_no>によって識別されます。

latchpageに対応するbufferpoolblockにあり、 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 lockNext-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の場合は、 recordX Next-key lock追加します。
  • 通常のINSERTの場合は、 recordS 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の場合は、 recordX Next-key lock追加します。
  • 通常のINSERTの場合は、 record2S 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と等しくない場合は、 recordGAP LOCKを追加し、NOT FOUNDを返します。
  • ここでは、レコードが search_tuple と等しいことを示し、レコードに Next-key Lock を追加します。2 つの例外を除いて、Rec Lock のみが追加されます。
  1. index_readの場合、現在のインデックスが主キーインデックスであり、 modePAGE_CUR_GEであり、 search_tupleのフィールド数がインデックス内の一意のフィールド数と等しい場合、
  2. unique_search であるかどうか、つまり search_tuple のフィールド数が現在のインデックスの一意のフィールド数と等しく、現在のインデックスが主キー インデックスであるか (セカンダリ インデックスであり、search_tuple に NULL フィールドが含まれていない)、レコードが削除マークではないかどうかを確認します。
  • これはロックが成功したことを示し、レコードが削除済みとしてマークされているケースを処理します。
  1. 現在のインデックスは主キーインデックスであり、 unique_searchであるため、 NOT FOUNDが返されます。
  2. それ以外の場合は、ステップ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 の場合、現在のページの最後のユーザー レコードと次のページの最初のユーザー レコード間のギャップを保護するためにギャップ ロックが追加されます。

プロセスには他には何もありません。

  1. 条件を満たすレコードの場合、デフォルトでは次キーロックが追加されます。
  2. セカンダリ インデックスがテーブルに返されると、プライマリ キーのみが再ロックされます。
  3. 特別なシナリオでは、一部のNext-key lock Rec lockにダウングレードされます (手順 5)
  4. ギャップロックのみが追加される特別なシナリオもあります(手順2と4)

要約:

上記は基本的にInnoDBにトランザクション ロックを追加するプロセスです。 InsertSelectのロック プロセスを組み合わせることで、トランザクション ロックの原理と実装が基本的に明らかになります。

これで、MySQL InnoDB トランザクション ロック ソース コード分析に関するこの記事は終了です。MySQL InnoDB トランザクション ロック ソース コード分析に関するより関連性の高いコンテンツについては、123WORDPRESS.COM の以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • MySQL Innodbの主な機能挿入バッファ
  • MySQL InnoDB ストレージエンジンのメモリ管理の詳細な説明
  • MySQL InnoDB ReplicaSet の簡単な紹介
  • MySQL Innodb インデックス メカニズムの詳細な紹介
  • MySQLのInnoDBストレージエンジンにおけるさまざまなロックの詳細な説明
  • MySQL ストレージ エンジン InnoDB と MyISAM
  • MySQL の innoDB でファントム リードを解決する方法

<<:  Tomcat サーバー入門の超詳細なチュートリアル

>>:  CSS の子要素を親要素と高い一貫性を持たせる方法

推薦する

Docker が elasticsearch を起動するときのメモリ不足の問題と解決策

質問Docker が elasticsearch をインストールして起動するときにメモリが不足するシ...

MySQL 5.6 圧縮パッケージのインストール方法

MySQL には、msi インストールと zip 解凍の 2 つのインストール方法があります。 zi...

Vue + Axios リクエストインターフェース方式とパラメータ渡し方式の詳しい説明

目次1. リクエストを取得する: 2. 投稿リクエスト: 3. 拡張と補足Vue スキャフォールディ...

Linux 環境に MySQL 8.0 をインストールするプロセスの紹介

目次序文1. Linux は yum ソースを変更します (MYSQL のインストールが遅い場合は試...

JavaScript の基礎: スコープ

目次範囲グローバルスコープ関数のスコープもし、スイッチ、のために、その間ブロックスコープスコープチェ...

Dockerコアとインストールの具体的な使い方

1. Docker とは何ですか? (1)DockerはLinuxコンテナ内でアプリケーションを実行...

CSS3 でテキスト ストロークを実装する 2 つの方法 (要約)

質問最近、以下に示すように、テキストストローク効果を実現するという要件に遭遇しました。 解決策1まず...

Vueはリストのシームレスなスクロールを実装します

この記事の例では、リストのシームレスなスクロールを実現するためのvueの具体的なコードを参考までに共...

Windows (コミュニティ エディション) に MySQL 8.0.18 をインストールするためのチュートリアル

この記事では、Windows で MySQL をインストールする方法について簡単に説明します。他にご...

Linux での MySQL データベースのマスター スレーブ同期レプリケーション構成

Linux での MySQL データベースのマスター/スレーブ同期構成の利点は、この方法をバックアッ...

Ubuntu 20.04 は Wi-Fi に接続します (2 つの方法)

最近Ubuntu 20.04をインストールしましたが、Wi-Fiに接続できず、Wi-Fiアイコンも表...

mysql8.0 Windows x64 zip パッケージのインストールと構成のチュートリアル

MySQL 8 Windows版 zipインストール手順(ダウンロードアドレス) 1. ZIPファイ...

Linux で Spring Boot プロジェクトを開始および停止するためのスクリプトの例

Springboot プロジェクトを開始するには、次の 3 つの方法があります。 1. メインメソッ...

Windows に Docker をインストールする詳細なチュートリアル

ローカルの MySQL バージョンが比較的低いため、最近 MySQL のバージョンをアップグレードす...

Nginxは特定のページへのIPアクセスを制限します

1.すべてのIPアドレスが3つのページa1.htm、a2.htm、a3.htmにアクセスするのを禁止...