MySQL マルチバージョン同時実行制御 MVCC の基本原理の分析

MySQL マルチバージョン同時実行制御 MVCC の基本原理の分析

1 トランザクションの同時実行で発生する問題

1.1 ダーティリード

トランザクションが、別のトランザクションによって変更されたがコミットされていないデータを読み取る場合、それはダーティ リードと呼ばれます。

1.2 繰り返し不可能な読み取り

トランザクション内で同じレコードが 2 回取得され、2 回取得された結果が異なる場合、この現象は非反復読み取りと呼ばれます。

1.3 ファントムリード

トランザクションが同じクエリ条件で 2 回 (または複数回) クエリを実行し、見つかったエントリの数に矛盾がある場合、ファントム リードと呼ばれます。

2つの分離レベル

同時トランザクションの実行中に発生する可能性のあるいくつかの問題を紹介しました。これらの問題にはそれぞれ異なる優先順位があります。これらの問題を重大度に応じてランク付けします。

ダーティリード > 反復不可能なリード > ファントムリード

SQL 標準では、異なる分離レベルに対して、同時トランザクションの重大度が異なる可能性があることが規定されています。

具体的な状況は以下のとおりです。

  • READ UNCOMMITTED: コミットされていない状態で読み取ります。
  • READ COMMITTED: コミットされた読み取り。
  • 繰り返し読み取り: 繰り返し読み取り。
  • シリアル化可能: シリアル化可能

SQL 標準では、異なる分離レベルでは、同時トランザクションで次のようにさまざまな重大度の問題が発生する可能性があることが規定されています。

  • READ UNCOMMITTED 分離レベルでは、ダーティ リード、反復不可能なリード、およびファントム リードが発生する可能性があります。
  • READ COMMITTED 分離レベルでは、非反復読み取りとファントム読み取りが発生する可能性がありますが、ダーティ読み取りは発生しません。
  • REPEATABLE READ 分離レベルでは、ファントム リードが発生する可能性がありますが、ダーティ リードと非反復可能リードは発生しません。
  • SERIALIZABLE 分離レベルでは、さまざまな問題が発生することはありません。

3 バージョンチェーン

InnoDB ストレージ エンジンを使用するテーブルの場合、クラスター化インデックス レコードには 2 つの必要な非表示列が含まれていることがわかっています (row_id は不要であり、作成するテーブルに主キーまたは NULL 以外の UNIQUE キーがある場合は row_id 列は含まれません)。

trx_id : トランザクションがクラスター化インデックス レコードを変更するたびに、トランザクションのトランザクション ID が trx_id 隠し列に割り当てられます。

roll_pointer : クラスター化インデックス レコードが変更されるたびに、古いバージョンが UNDO ログに書き込まれ、この非表示の列は、レコードが変更される前の情報を検索するために使用できるポインターとして機能します。

挿入されたレコードのトランザクション ID が 80 であると仮定すると、この時点でのレコードの概略図は次のようになります。

トランザクション ID 100 と 200 を持つ 2 つのトランザクションがこのレコードに対して UPDATE 操作を実行するとします。操作フローは次のようになります。

TRX100:

UPDATE t_people SET name = 'Guan Yu' WHERE number = 1;
UPDATE t_people SET name = '张飞' WHERE number = 1;

TRX200:

UPDATE t_people SET name = 'Zhao Yun' WHERE number = 1;
UPDATE t_people SET name = 'Zhuge Liang' WHERE number = 1;

レコードが変更されるたびに、undo ログが記録されます。各 undo ログには roll_pointer 属性もあります (レコードに以前のバージョンがないため、INSERT 操作に対応する undo ログにはこの属性がありません)。これらの undo ログは接続してリンク リストを形成できるため、現在の状況は次の図のようになります。

レコードが更新されるたびに、古い値が、たとえレコードの古いバージョンであっても、元に戻すログに入れられます。更新回数が増えると、すべてのバージョンが roll_pointer 属性によってリンク リストに接続されます。このリンク リストをバージョン チェーンと呼び、バージョン チェーンの先頭ノードは現在のレコードの最新の値です。さらに、各バージョンには、バージョンの世代に対応するトランザクション ID も含まれます。このレコードのバージョン チェーンは、同じレコードにアクセスする同時トランザクションの動作を制御するために使用できます。このメカニズムは、多版本并發控制(Mulit-Version Concurrency Control MVCC)と呼ばれます。

4 読み取りビュー

4.1 ReadView の定義

InnoDB は、主に次の 4 つの重要な内容を含む ReadView の概念を提案しています。

  • (1)m_ids:ReadViewが生成された時点での現在のシステム内のアクティブな読み取りおよび書き込みトランザクションのトランザクションIDリストを表します。
  • (2)min_trx_id:ReadViewが生成された時点での現在のシステム内のアクティブな読み取りおよび書き込みトランザクションの中で最小のトランザクションID、つまりm_idsの最小値を示します。
  • (3)max_trx_id:ReadViewを生成するときにシステム内の次のトランザクションに割り当てるID値を示します。 max_trx_id は m_ids の最大値ではありません。トランザクション ID は増分的に割り当てられます。たとえば、ID 1、2、3 の 3 つのトランザクションがあり、ID 3 のトランザクションがコミットされます。次に、新しい読み取りトランザクションによって ReadView が生成されると、m_ids には 1 と 2 が含まれ、min_trx_id の値は 1、max_trx_id の値は 4 になります。
  • (4)creator_trx_id: ReadViewを生成したトランザクションのトランザクションIDを示します。

4.2 アクセス制御

この ReadView を使用すると、レコードにアクセスするときに、レコードのバージョンが表示されているかどうかを判断するには、次の手順に従うだけで済みます。

  • (1) アクセスされたバージョンのtrx_id属性値がReadViewのcreator_trx_id値と同じ場合、現在のトランザクションが変更したレコードにアクセスしていることを意味し、そのバージョンは現在のトランザクションからアクセスできます。
  • (2)アクセスされたバージョンのtrx_id属性値がReadViewのmin_trx_id値より小さい場合、このバージョンを生成したトランザクションは現在のトランザクションがReadViewを生成する前にコミットされているため、このバージョンは現在のトランザクションからアクセスできることを示しています。
  • (3) アクセスされたバージョンのtrx_id属性値がReadViewのmax_trx_id値以上である場合、このバージョンを生成したトランザクションは現在のトランザクションがReadViewを生成した後に開かれたため、このバージョンは現在のトランザクションからはアクセスできないことを示します。
  • (4) アクセスしたバージョンのtrx_id属性値がReadViewのmin_trx_idとmax_trx_idの間(min_trx_id < trx_id < max_trx_id)にある場合、trx_id属性値がm_idsリストにあるかどうかを判断する必要があります。ある場合、ReadView作成時にバージョンを生成したトランザクションがまだアクティブであり、バージョンにアクセスできないことを意味します。ない場合は、ReadView作成時にバージョンを生成したトランザクションがコミットされており、バージョンにアクセスできることを意味します。
  • (5)あるバージョンのデータが現在のトランザクションで可視でない場合は、バージョンチェーンをたどって次のバージョンのデータを探し、上記の手順に従って可視性を判定し続け、バージョンチェーンの最後のバージョンまでこれを繰り返します。最後のバージョンも表示されない場合は、レコードがトランザクションに対して完全に不可視であり、クエリ結果にレコードが含まれないことを意味します。

4.3 隔離の再考

READ UNCOMMITTED 分離レベルを使用するトランザクションの場合、コミットされていないトランザクションによって変更されたレコードを読み取ることができるため、レコードの最新バージョンを直接読み取ることができます。

SERIALIZABLE 分離レベルを使用するトランザクションの場合、InnoDB はロックを使用してレコードにアクセスします。

MySQL では、READ COMMITTED 分離レベルと REPEATABLE READ 分離レベルの大きな違いは、ReadView が生成されるタイミングです。

4.3.1 コミットされた読み取り

読み取りが送信されると、各データの読み取りの前に ReadView が生成されます。

ここで、READ COMMITTED 分離レベルを使用するトランザクションの実行が開始されるとします。

詳細なクエリ:

# READ COMMITTED 分離レベルを使用するトランザクション # トランザクション 100 と 200 はコミットされておらず、列名の値は Liu BeiSELECT name FROM t_people WHERE number = 1; です

この SELECTET の実行プロセスは次のとおりです。

  • (1) SELECT文を実行すると、まずReadViewが生成されます。ReadViewのm_idsリストの内容は[100, 200]、min_trx_idは100、max_trx_idは201、creator_trx_idは0です。
  • (2)次に、バージョンチェーンから表示可能なレコードを選択します。図からわかるように、最新バージョンのname列の内容はZhuge Liangです。このバージョンのtrx_id値は200で、m_idsリストにあるため、可視性要件を満たしていません。 (アクセスしたバージョンの trx_id 属性値が ReadView の min_trx_id と max_trx_id の間にある場合、trx_id 属性値が m_ids リスト内にあるかどうかを判断する必要があります。存在する場合、ReadView の作成時にバージョンを生成したトランザクションがまだアクティブであり、バージョンにアクセスできないことを意味します。存在しない場合、ReadView の作成時にバージョンを生成したトランザクションがコミットされており、バージョンにアクセスできることを意味します)、roll_pointer に従って次のバージョンにジャンプします。
  • (3)次バージョンの諸葛亮の名前欄の内容は趙雲です。このバージョンのtrx_id値も200で、m_idsリストにもあります。そのため、要件を満たさず、次のバージョンにジャンプし続けます。
  • (4)趙雲の次のバージョンの名前欄の内容は張飛です。このバージョンのtrx_id値も100で、m_idsリストにもあります。そのため、要件を満たさず、次のバージョンにジャンプし続けます。
  • (5)張飛の次のバージョンの名前列の内容は関羽です。このバージョンのtrx_id値も100であり、これもm_idsリストにあります。したがって、要件を満たさず、次のバージョンにジャンプし続けます。
  • (6)関羽の次のバージョンは劉備です。このバージョンのtrx_id値は80で、ReadViewのmin_trx_id値よりも小さいため、このバージョンは要件を満たしています。

繰り返し不可能な読み取り: トランザクション 100 および 200 が開始されると、読み取られる名前は Liu Bei になります。トランザクション100がコミットされると、トランザクション分離レベルが読み取りコミットであるため、読み取りごとにReadViewが作成されます。トランザクション200が読み取られると、生成されたReadView m_idsは[200]になります。このとき、読み取りルールに従って読み取られた名前はZhang Feiです。

# READ COMMITTED 分離レベルでトランザクション BEGIN を使用します。
# SELECE1: トランザクション 100 と 200 は送信されず、名前の値は Liu Bei です。SELECT name FROM t_people WHERE number = 1; 

# SELECE2: トランザクション 100 はコミットされ、トランザクション 200 はコミットされません。# トランザクション 200 のトランザクション クエリでは、名前の値は Zhang Fei であり、反復不可能な読み取りが発生します。
SELECT name FROM teacher WHERE number = 1;

4.3.2 繰り返し読み取り

再読み取り可能。初めてデータを読み取るときに ReadView を生成します。

繰り返し不可能な読み取りの問題を解決します。100 トランザクションと 200 トランザクションが有効になり、ReadView が作成されます。m_ids は [100,200] で、読み取られた名前は Liu Bei です。トランザクション100がコミットされると、再読み取りトランザクション分離レベルにより、ReadViewは1つだけ作成され、m_idsは[100,200]のままです。このとき、読み取りルールに従って読み取られた名前はLiu Beiのままです。

5 ファントムリード

トランザクションが同じクエリ条件で 2 回 (または複数回) クエリを実行し、見つかったエントリの数に矛盾がある場合、ファントム リードと呼ばれます。

REPEATABLE READ 分離レベルのトランザクション T1 は、まず検索条件に基づいて複数のレコードを読み取り、次にトランザクション T2 は対応する検索条件を満たすレコードを挿入してコミットし、最後にトランザクション T1 は同じ検索条件に基づいてクエリを実行します。結果はどうなるでしょうか? ReadView の比較ルールによれば、トランザクション T2 がトランザクション T1 より前に開始されたかどうかに関係なく、トランザクション T1 は T2 のコミットを確認できません。ただし、REPEATABLE READ 分離レベルの InnoDB の MVCC では、ファントム リードを完全に禁止するのではなく、ファントム リードを大幅に回避できます。

#SELECT: スナップショットを読み取りました。更新: 現在既読。
REPEATABLE READ は、スナップショット読み取りのファントム読み取り問題を解決できます。現在のファントムリード問題は解決できません。

例:

1 beginを実行し、select *を実行します。

2 別のウィンドウを開いて、挿入、選択、コミットを実行します。

3 元のウィンドウに戻ってクエリを実行します

4 更新を実行して送信する

5 検索

6 結論

上記の説明から、いわゆる MVCC (Multi-Version Concurrency Control) は、READ COMMITTD と REPEATABLE READ の 2 つの分離レベルでトランザクションを使用して通常の SELECT 操作を実行するときに、レコードのバージョン チェーンにアクセスするプロセスを指すことがわかります。これにより、異なるトランザクションの読み取り書き込み操作と書き込み読み取り操作を同時に実行できるため、システム パフォーマンスが向上します。

READ COMMITTD と REPEATABLE READ という 2 つの分離レベルの大きな違いは、ReadView が生成されるタイミングです。READ COMMITTD は、通常の SELECT 操作の前に ReadView を生成しますが、REPEATABLE READ は、最初の通常の SELECT 操作の前にのみ ReadView を生成し、後続のクエリ操作ではこの ReadView を再利用するため、基本的にファントム リードを回避できます。

以上がこの記事の全内容です。皆様の勉強のお役に立てれば幸いです。また、123WORDPRESS.COM を応援していただければ幸いです。

以下もご興味があるかもしれません:
  • MySQLクエリキャッシュメカニズムの基礎学習チュートリアル
  • MySQL セレクトキャッシュメカニズムの使用に関する詳細な説明
  • MySQL マルチバージョン同時実行制御 MVCC の詳細な研究
  • MySQL マルチバージョン同時実行制御 MVCC の実装
  • MYSQL トランザクション分離レベルと MVCC
  • MySQL の MVCC と BufferPool キャッシュ メカニズムの詳細な理解

<<:  HTML で div+CSS を使用してシンプルな矢印アイコンを実装するコード

>>:  レンダリング関数を使用して、拡張性の高いコンポーネントをカプセル化する

推薦する

MySQL データベースのマスター スレーブ分離のサンプル コード

導入MySQL データベースの読み取りと書き込みの分離を設定すると、データベースに対する書き込み操作...

Vue の親子コンポーネントの値転送と一方向データフローの問題の詳細な説明

目次序文1. 親コンポーネントが子コンポーネントに値を渡す2. サブコンポーネントのprops型制約...

Django は Pillow を使用して検証コード機能を簡単に設定します (Python)

1. モジュールをインポートし、検証状態を定義する PIL から Image、ImageDraw、...

ソースコードから MySQL 8.0.20 をコンパイルしてインストールする詳細なチュートリアル

前回の記事では、次のことを紹介しました。 MySQL8.0.20 インストール チュートリアルとイン...

Vue3 のレンダリング関数における互換性のない変更の詳細な説明

目次レンダリングAPIの変更レンダリング関数のパラメータレンダリング関数のシグネチャの変更VNode...

Navicat PremiumでSQLファイルをインポートする方法

今日、最終プロジェクトに取り組み始めましたが、今年はMySQLデータベースを使用したため、Navic...

Dockerサーバーのストレージリソースプール不足問題の解決

目次1. 問題の説明2. 問題分析3. 問題解決1. Dockerのディスク使用量を確認する2. 再...

MySQLを閉じることができない問題を解決する方法

mysql が閉じない場合の解決策:コンピュータのタスクバーを右クリックしてタスクマネージャーを開き...

Nginx URL 書き換えメカニズムの原理と使用例

URL 書き換えは、Web サイトの優先ドメインを決定するのに役立ちます。同じリソース ページの複数...

Ubuntu での CUDA と CUDNN のインストールとアンインストールの実装

目次序文グラフィックドライバーをインストールするCUDAをアンインストールするCUDAをインストール...

JSはキャンバス技術を使用してeChartsの棒グラフを模倣します

Canvas は HTML5 の新しいタグです。js を使用して Canvas 描画 API を操作...

Vue の動的メニュー、動的ルートの読み込みと更新の落とし穴

目次必要:アイデア:レッスン:テキストを共有する:要約する必要:インターフェイスからサブメニュー デ...

Firefox で英語の文字が折り返されない問題の解決方法

テキストのレイアウトには、言語に応じていくつかの書式設定要件があります。たとえば、簡体字中国語では、...

MySQL 5.6 ルートパスワード変更チュートリアル

1. MySQL 5.6をインストールした後、正常に有効化できないMySQL の圧縮バージョンは、解...

イメージのパッケージ化とワンクリック展開を実現するためにDockerを組み合わせたアイデア

1. サーバーにDockerをインストールする yumでdockerをインストール設定ファイルを変更...