MySQL マルチバージョン同時実行制御メカニズム (MVCC) ソースコードの詳細な説明

MySQL マルチバージョン同時実行制御メカニズム (MVCC) ソースコードの詳細な説明

1. はじめに

データベース愛好家として、私はシンプルな SQL パーサーとストレージ エンジンを自分で作成しましたが、まだ十分満足できるものではないと感じています。 <<トランザクション処理 - 概念とテクノロジ>> は確かに非常に詳細ですが、一般的なアイデアしか提供できず、実際のデータベースを操作するのには役立ちません。 cmake のおかげで、xcode を使用して Mac 上で MySQL をデバッグできるようになり、さまざまな実装の詳細を理解できるようになりました。

(注: この記事で使用されている MySQL のバージョンは MySQL-5.6.35 です)

2. MVCC (マルチバージョン同時実行制御メカニズム)

分離は、同時実行制御、直列化可能性などとも呼ばれます。同時実行制御といえば、まずロックが思い浮かびます。MySQL では、2 フェーズ ロックを使用して更新の直列化を実装しています。同時に、クエリのパフォーマンスを高速化するために、MVCC (Multi Version Concurrency Control) メカニズムが採用されており、ロックなしでも一貫性のあるバージョンを取得できます。

2.1 繰り返し読み取り

MySQL は、MVCC と Next-Key Lock を通じて Repeatable Read を実装します。MVCC の考え方は、データのバージョン変更を記録し、異なるバージョンのデータを巧みに選択することで、一貫した結果をユーザーに提示することです。次の図に示すように:

上の図では、(A=50|B=50) の初期バージョンは 1 です。

1. トランザクションt1がAを選択すると、バージョン1、つまりA=50が表示されます。

2. トランザクションt2はAとBを変更し、バージョンを2にアップグレードします。つまり、A=0、B=100です。

3. トランザクションt1がBを再度選択すると、バージョンは1のまま、つまりB=50となる。

これにより、バージョンの影響が分離され、A+B は常に 100 になります。

2.2 コミットの読み取り

最新のコミット結果がバージョン管理メカニズムなしで読み取られる場合、次の図に示すように、分離レベルは読み取りコミットになります。

この場合、次の図に示すように、正確で一貫性のある結果を得るために、ロック メカニズム (select for update など) を使用して A レコードと B レコードをロックする必要があります。

2.3 MVCCの利点

アカウントが揃っているかどうかを確認するなど、一貫性をチェックするために一部のデータに対して読み取り専用操作を実行する必要がある場合、パフォーマンスの大幅な低下を引き起こすロックを追加したくありません。現時点では、MVCC の一貫性のあるバージョンには大きな利点があります。

3. MVCC(実装メカニズム)

このセクションでは、MVCC の実装メカニズムについて説明します。MVCC は純粋な選択に対してのみ有効であることに注意してください (更新のための選択、共有モードでのロック、更新\挿入などのロック操作は除く)。

3.1、実行中のスタックを選択する

まず、MySQL ソース コード内の一般的なクエリ SQL の実行プロセスを追跡してみましょう。SQL は (select * from test); です。

実行中のスタックは次のとおりです。

handle_one_connection MySQLのネットワークモデルは1つのリクエストに1つのスレッドです

|-接続を1つ処理する

|-コマンドを実行する

|-ディスパッチコマンド

|-mysql_parse SQLの解析

|-mysql_execute_command

|-execute_sqlcom_select 選択ステートメントを実行する

|-ハンドル選択

... 解析結合やその他の操作が多数ありますが、現時点では気にしていません

|-*tab->read_record.read_record レコードの読み取り

MySQLのデフォルトの分離レベルはrepeatable_read (RR)なので、read_recordは次のようにオーバーロードされます。
rr_sequential (現在、インデックスをスキャンして行を選択し、条件でフィルタリングするプロセスについては考慮していません)。追跡を続ける:

レコードの読み取り

|-rr_シーケンシャル

|-ha_rnd_next

|-ha_innobase::rnd_next これは InnoDB エンジンです。

|-一般フェッチ

|-mysqlの行検索

|-lock_clust_rec_cons_read_sees ここでバージョンが決定され選択される

この関数の内部を見てみましょう:

bool lock_clust_rec_cons_read_sees(const rec_t* rec /*innodbによってスキャンされた行*/,....){
	...
	// 現在スキャンされている行から最後に変更されたバージョンtrx_id(トランザクションID)を取得します
	trx_id = row_get_rec_trx_id(rec、インデックス、オフセット);
	// パラメータによって参照される行スナップショットを決定します (一貫性のあるスナップショット ビューとトランザクション ID) return(read_view_sees_trx_id(view, trx_id));
}

3.2. read_viewの作成プロセス

まず、一貫性ビューの作成プロセスに焦点を当ててみましょう。まず、read_view 構造を見てみましょう。

構造体read_view_t{
	// 逆順なので、low/up が逆になります // 現在の行バージョンの最高水準点を確認できます。また、>= low_limit_id は確認できません trx_id_t low_limit_id;
	// 現在の行バージョンの最低水準点 (< up_limit_id ) を確認できます trx_id_t up_limit_id;
	// 現在アクティブなトランザクション(コミットされていないトランザクション)の数 ulint n_trx_ids;
	// 現在アクティブなトランザクション ID の配列を逆順に取得します // up_limit_id<tx_id<low_limit_id
	trx_id_t* trx_id;	
	// 現在のビューのトランザクションIDを作成する
	trx_id_t 作成者_trx_id;
	//トランザクション システム内の一貫性ビュー リスト UT_LIST_NODE_T(read_view_t) view_list;
};

その後、デバッグにより、read_view 構造体の作成も上記の rr_sequential で操作され、呼び出しスタックが継続されることがわかります。

rr_シーケンシャル

|-ha_rnd_next

|-rnd_next

|-index_first start_of_scanがtrueの場合、現在のブランチindex_firstに移動する

|-インデックス読み取り

|-mysqlの行検索

|-trx_assign_read_view

row_search_for_mysql のブランチを見てみましょう。

mysqlの行検索:
// 一貫性ビューは、ロックフリー モードが選択されている場合にのみ作成されます。else if (prebuilt->select_lock_type == LOCK_NONE) { // 一貫性ビューを作成します。trx_assign_read_view(trx);
		事前構築済み->sql_stat_start = FALSE;
}

上記のコメントは、select for update (共有モデル内) が MVCC に従わない理由です。 trx_assign_read_view 関数をさらに分析してみましょう。

trx_assign_read_view

|-read_view_open_now

|-read_view_open_now_low

さて、ようやく read_view を作成するメインの段階に到達しました。メインのプロセスを次の図に示します。

コードプロセスは次のとおりです。

静的 read_view_t* read_view_open_now_low(trx_id_t cr_trx_id、mem_heap_t* ヒープ)
{
	read_view_t* ビュー;
	// 現在のトランザクションシステムでは、max_trx_id(割り当てられていないtrx_id)はlow_limit_noに設定されます
	ビュー->low_limit_no = trx_sys->max_trx_id;
	ビュー->low_limit_id = ビュー->low_limit_no;
	// CreateView コンストラクターは、現在のトランザクション以外と、メモリにコミットされたトランザクションを削除します。つまり、判断条件は // trx->id != m_view->creator_trx_id&& !trx_state_eq(trx, TRX_STATE_COMMITTED_IN_MEMORY) です。 // 次に、それらを現在のビュー リスト ut_list_map(trx_sys->rw_trx_list, &trx_t::trx_list, CreateView(view)); に追加します。
	ビュー->n_trx_idsが0より大きい場合
		// 現在のトランザクション システムの最小 ID を up_limit_id に設定します。これは逆順であるためです。view->up_limit_id = view->trx_ids[view->n_trx_ids - 1];
	} それ以外 {
		// 現在のトランザクション以外にアクティブなトランザクションがない場合は、low_limit_id に設定します
		ビュー->上限id = ビュー->下限id;
	}
	// パージトランザクションを無視します。パージ時、現在のトランザクションIDは0です
	(cr_trx_id > 0)の場合{
		read_view_add(ビュー);
	}
	// 一貫性のあるビューを返します return(view);
}

3.3. 行バージョンの可視性

上記の lock_clust_rec_cons_read_sees から、行バージョンの可視性は read_view_sees_trx_id 関数によって決定されることがわかります。

/****************************************************************************//**
読み取りビューが指定されたトランザクションを参照するかどうかを確認します。
見つかった場合は @return true */
ユニバーインライン
ブール
read_view_sees_trx_id(
/*====================*/
	const read_view_t* view, /*!< in: ビューの読み取り */
	trx_id_t trx_id) /*!< in: trx id */
{
	if (trx_id < view->up_limit_id) {

		戻り値(true);
	} そうでない場合 (trx_id >= view->low_limit_id) {

		戻り値(false);
	} それ以外 {
		ulint 下限 = 0;
		ulint 上位 = view->n_trx_ids - 1;

		ut_a(ビュー->n_trx_ids> 0);

		する {
			ulint 中間 = (下限 + 上限) >> 1;
			trx_id_t mid_id = view->trx_ids[mid];

			(mid_id == trx_id)の場合{
				戻り値(FALSE);
			} そうでない場合 (mid_id < trx_id) {
				中間値 > 0 の場合 {
					上 = 中 - 1;
				} それ以外 {
					壊す;
				}
			} それ以外 {
				下 = 中 + 1;
			}
		} while (下限 <= 上限);
	}

	戻り値(true);
}

実際、上記の関数はバイナリ検索です。read_view は、現在アクティブなトランザクションのすべてのトランザクション ID を実際に保存します。現在の行バージョンの変更に対応するトランザクション ID が現在アクティブなトランザクションにない場合は、次の図に示すように、現在のバージョンが表示されていることを示す true を返し、そうでない場合は非表示になります。

上記の lock_clust_rec_cons_read_sees の戻りに続いて:

(UNIV_LIKELY(srv_force_recovery < 5)の場合
			    && !lock_clust_rec_cons_read_sees(
				    rec、インデックス、オフセット、trx->read_view)){
	// 現在、現在のバージョンが表示されない状況を処理しています // undolog を使用して、一貫性のある表示バージョンに戻ります err = row_sel_build_prev_vers_for_mysql(
					trx->read_view、clust_index、
					事前構築済み、rec、&offsets、&heap、
					&old_vers、&mtr);			    
} それ以外{
	// 表示したら戻ります }

3.4. 表示バージョンのUndolog検索プロセス

ここで、row_sel_build_prev_vers_for_mysql 関数を調べてみましょう。

行選択ビルドの以前のバージョンをmysql用

|-row_vers_build_for_consistent_read

主に、 row_ver_build_for_consistent_read メソッドが呼び出され、表示可能なバージョンが返されます。

dberr_t row_vers_build_for_consistent_read(...)
{
	......
	のために(;;){
		err = trx_undo_prev_version_build(rec、mtr、バージョン、インデックス、*オフセット、ヒープ、&prev_version);
		......
		trx_id = row_get_rec_trx_id(前のバージョン、インデックス、*オフセット);
		// 現在の行バージョンが一貫性のあるビューに準拠している場合は、if (read_view_sees_trx_id(view, trx_id)) { を返します。
			......
			壊す;
		}
		// 現在の行バージョンが一致しない場合は、前のバージョンに戻って続行します(for ループに戻ります)
		バージョン = 前のバージョン;
	}
	......
}

全体のプロセスを下の図に示します。

undolog が行レコードの対応するバージョンを復元する方法については、複雑なプロセスです。スペースの制約により、ここでは省略します。

3.5. read_view を作成するタイミングについて議論する

一貫性のあるビューを作成するrow_search_for_mysqlのコードでは

// 非ロック モードでのみ選択すると、一貫性のあるビューが作成されます。else if (prebuilt->select_lock_type == LOCK_NONE) { // 一貫性のあるビューを作成します。trx_assign_read_view(trx);
		事前構築済み->sql_stat_start = FALSE;
}

trx_assign_read_viewにそのようなコードがあります

// 一貫性のあるビューはトランザクション内で一度だけ作成されます if (!trx->read_view) {
		trx->read_view = read_view_open_now(
			trx->id、trx->global_read_view_heap);
		trx->global_read_view = trx->read_view;
	}

したがって、トランザクション内でこれら 2 つのコードを組み合わせると、次の図に示すように、SELECT が初めて実行されたとき (ロックなし) にのみ一貫性のあるビューが作成されます。

著者はそのようなシナリオを構築し、シミュレーションしましたが、それは確かに真実です。

4. MVCCとロックの同時動作によって引き起こされるいくつかの現象

MySQL は、パフォーマンスと一貫性のバランスをとるために MVCC と 2 フェーズ ロック (2PL) を使用します。ただし、MySQL は選択時にのみ一貫性のあるビューを作成し、更新などのロック操作時には一貫性のあるビューを作成しないため、奇妙な現象が発生します。次の図に示すように:

更新は一貫性のあるビュー (read_view) に従わないが、選択は一貫性のあるビュー (read_view) に従うことを理解すれば、この現象はうまく説明できます。
次の図に示すように:

V. 結論

MySQL はパフォーマンスのバランスをとるために多くの複雑なメカニズムを使用しており、ACID、2PL (2 フェーズ ロック)、MVCC はその実装の典型的な例です。幸いなことに、Xcode などの IDE を介してデバッグすると便利なので、さまざまなメカニズムの実装を非常に正確かつ便利に追跡できます。この記事が、MySQL ソースコードを勉強したい読者の役に立つことを願っています。

上記はMySQLマルチバージョン同時実行制御メカニズム(MVCC)ソースコードの詳細な説明です。MySQL同時実行制御メカニズムMVCCの詳細については、123WORDPRESS.COMの他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • MySQL マルチバージョン同時実行制御 MVCC の実装
  • MySQLのMVCCマルチバージョン同時実行制御の実装
  • MySQL マルチバージョン同時実行制御 MVCC の詳細な研究
  • MySQL マルチバージョン同時実行制御 MVCC の基本原理の分析
  • MySQL マルチバージョン同時実行制御 MVCC の実装
  • Mysql MVCC マルチバージョン同時実行制御の詳細

<<:  nginx と openssl で https を実装する方法

>>:  CSS における位置指定の概要

推薦する

ハイパーリンクの表示と開き方

<br />関連記事: ハイパーリンクを表示して開く方法症状<br />ユー...

Vue3 非同期データ読み込みコンポーネントサスペンスの使い方

目次序文コンポーネントの作成要約する序文Vue3 には多くの注目すべき機能が追加されましたが、サスペ...

Ubuntu にグラフィック ドライバーが正常にインストールされたかどうかを確認する方法

次のコマンドを実行します: glxinfo | grep レンダリング結果が「はい」の場合、グラフィ...

HTML でさまざまなスペースの特徴と表現を探る (推奨)

I. 概要HTML テンプレートを作成するときに、テキスト レイアウトの手段としてスペースが使用さ...

MySQLデータベースインデックスの詳細な紹介

目次マインドマップシンプルな理解インデックスモデルの進化二分探索木自己バランス型二分木BツリーB+ ...

CSS3 の transition、transform、translate の違いと機能の簡単な分析

変換して翻訳するTransform は、変換と変形を意味します。他の幅属性や高さ属性と同様に、CSS...

jsを使用して簡単な抽選機能を実現する

この記事では、参考までに、簡単な抽選機能を実装するためのjsの具体的なコードを共有します。具体的な内...

HTMLチュートリアル、簡単に学べるHTML言語

1. <body background=画像ファイル名 bgcolor=color text=...

Ubuntu 20.04でLNMP環境を構築する方法

簡単な説明以前 Centos7 で構築し、その後個人開発環境として Ubuntu 20.04 を使っ...

Linuxシステムにおけるキー認証に基づくSSHサービスのプロセス

ご存知のとおり、SSH は現在、リモート ログイン セッションやその他のネットワーク サービスにセキ...

MySQL における大規模オブジェクトのマルチバージョン同時実行制御の詳細な説明

MySQL 8.0: InnoDB のラージ オブジェクトに対する MVCCこの記事では、MySQL...

文字列から指定された文字を削除または抽出する JavaScript メソッド (非常によく使用されます)

目次1. 部分文字列() 2. サブストラクチャ() 3.インデックス() 4.最後のインデックス(...

MySQLでデータベースのインストールパスを表示する方法

mysql コマンドを使用して、mysql のインストール パスを表示できます。 # 次の 2 つの...

Dockerを使用してSpringBootプロジェクトをデプロイする方法

Docker テクノロジの開発により、マイクロサービスの実装にさらに便利な環境が提供されます。Doc...

関連するプロパティのリストを含む HTML エリア イメージ ホットスポットの使用の概要

<area> タグは主にイメージマップで使用されます。イメージマップにアクティブ領域 (...