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 における位置指定の概要

推薦する

WindowsにOpenSSHをインストールし、SSHキーを生成してLinuxサーバーにログインします。

SSH の正式名称は Secure SHell です。 SSH を使用すると、送信されるすべてのデ...

MySQL 一時テーブルの簡単な使用法

MySQL 一時テーブルは、一時的なデータを保存する必要がある場合に非常に便利です。一時テーブルは現...

W3C チュートリアル (15): W3C SMIL アクティビティ

SMIL は、Web にタイミングとメディアの同期のサポートを追加します。 SMIL は、Web に...

Reactのdiffアルゴリズムの詳細な分析

Reactのdiffアルゴリズムの理解diffアルゴリズムは、 Virtual DOMの変更された部...

Nginx プロセス スケジューリングの問題の詳細な説明

Nginx は、マスター プロセス (MasterProcess) と、同じ数のホスト CPU コア...

CSS3は三角形の連続拡大効果を実現します

1. CSS3の三角形は特殊効果でズームし続けます11.1 画像プレビュー 11.2 index.h...

HTML CSS JS はタブページのサンプルコードを実装します

コードをコピーコードは次のとおりです。 <html xmlns="">...

今日は、珍しいけれど役に立つJSテクニックをいくつか紹介します

1. 戻るボタンhistory.back() を使用してブラウザの「戻る」ボタンを作成します。 &l...

CSSを使用してダークモードとブライトモードを切り替える

Web Skills第5号では、CSSでダークモードやハイライトモードを実装するための技術的なソリュ...

TypeScript デコレータ定義

目次1. コンセプト1.1 定義1.2 デコレータファクトリー1.3 デコレータの組み合わせ1.4 ...

InnoDBのインデックスページ構造、挿入バッファ、適応ハッシュインデックスについての簡単な説明

InnoDB インデックスの物理構造すべての InnoDB インデックスは Btree インデックス...

ウェブサイトを高速化する

パフォーマンスは本当に重要ですか?パフォーマンスは重要であり、誰もがそれを知っています。なぜ私たちは...

MySQLオンラインデータベースのデータをクリーンアップする方法

目次01 シナリオ分析02 操作方法03 結果分析01 シナリオ分析今日の午後、開発仲間がオンライン...

MySql8.0 のトランザクション分離レベルエラーの問題を解決する

目次MySql8.0 トランザクション分離レベルエラーの表示質問コマンドは次のように変更されますMy...

Javascript 構造化代入の詳細

目次1. 配列の分解2. オブジェクトの分解3. 不完全な解体4. 分割代入を使用して変数交換を実装...