MVCC (Multiversion Concurrency Control)、マルチバージョン同時実行制御
とは何ですか。名前が示すように、MVCC はデータ行の複数のバージョン管理を通じてデータベースの 同時実行制御
を実装します。このテクノロジーにより、InnoDB のトランザクション分離レベルでの一貫した読み取り操作が保証されます。つまり、別のトランザクションによって更新されている一部の行をクエリすることで、更新前の値を確認できるため、クエリを実行するときに別のトランザクションがロックを解放するのを待つ必要がありません。 。 MVCC には正式な標準はありません。MVCC の実装は DBMS ごとに異なる場合があり、普遍的に使用されるわけではありません (関連する DBMS ドキュメントを参照してください)。ここでは、InnoDB での MVCC の実装メカニズムについて説明します (他の MySQL ストレージ エンジンはサポートしていません)
2 スナップショットの読み取りと現在の読み取り
を処理するより良い方法を使用して、読み取り/書き込み競合がある場合でも、ロックなし
、ノンブロッキング同時読み取りを実行できるようにします。
を達成しました。この読み取りは、現在の読み取り
ではなく、 スナップショットの読み取り
を参照します。現在の読み取りは実際にはロック操作であり、悲観的ロックの実装です。 MVCC の本質は、楽観的なロック思考を使用する方法です。 2.1 スナップショット読み取り
、 はロックなしの非ブロッキング読み取りです。例:
select * from player where ...
複数のバージョンに基づいているため、スナップショットの読み取りは必ずしも最新バージョンのデータを読み取るとは限りませんが、以前の履歴バージョンである可能性があります。
スナップショット読み取りの前提は、分離レベルがシリアル レベルではないことです。シリアル レベルでのスナップショット読み取りは、現在の読み取りに縮退します。
2.2 現在の読み取り値
3. レビュー
MysQL では、デフォルトの分離レベルは反復読み取りであり、ダーティな問題を解決できます。読み取りの問題を定義の観点からのみ見た場合、ファントム 読み取りの問題は解決されません。ファントム読み取りの問題を解決したい場合は、シリアル化を使用する必要があります。つまり、分離レベルを最高レベルに上げる必要がありますが、これによりデータベースのトランザクション同時実行機能が大幅に低下します。
MVCC はロック メカニズムを使用できませんが、オプティミスティック ロックを通じて非反復読み取りとファントム読み取りの問題を解決します! ほとんどの場合、行レベルのロックを置き換えることができ、システムのオーバーヘッドを削減できます。
3.2 隠しフィールド、Undo ログ バージョン チェーン
trx_id: トランザクションがクラスター化インデックス レコードを変更するたびに、トランザクションのトランザクション ID が trx_id 非表示列に割り当てられます。 roll_pointer: クラスター化インデックスのレコードが変更されるたびに、古いバージョンが元に戻すログに書き込まれます。この場合、この非表示列はポインターに相当し、レコードが見つかる前の情報が変更されます。
4. ReadView の MVCC 実装原則
4.1 ReadView とは
MVCC メカニズムでは、複数のトランザクションのペアが更新されます。同じ行レコードから複数の履歴スナップショットが生成され、これらの履歴スナップショットは Undo ログに保存されます。トランザクションがこの行レコードをクエリする必要がある場合、行レコードのどのバージョンを読み取る必要がありますか? 現時点では、行の可視性の問題の解決に役立つ ReadView を使用する必要があります。トランザクションが MVCC メカニズムを使用してスナップショット読み取り操作を実行すると、読み取りビューが生成されます。このビューは ReadView です。トランザクションが開始されると、データベース システムの現在のスナップショットが生成されます。InnoDB は、各トランザクションの配列を構築して、システム内の現在アクティブなトランザクションの ID を記録および維持します (「アクティブ」とは、開始されているがまだ送信されていないことを指します)。 ).
READ UNCOMMITTED
分離レベルのトランザクションを使用します。コミットされていないトランザクションによって変更されたレコードを読み取ることができるため、トランザクションの最新のレコードを直接読み取ることができます。レコードです。バージョンは問題ありません。
使用 SERIALIZABLE
分離レベル トランザクション。InnoDB では、レコードにアクセスするためにロックを使用することが規定されています。
使用 READ COMMITTED
分離レベル REPEATABLE READ
のトランザクションは、 によって変更されたレコードがトランザクション
によって送信されたことを確認する必要があります。別のトランザクションがレコードを変更したが、まだ送信していない場合は、そのレコードを読み取ることはできません。レコードの最新バージョンを直接読み取る場合、最も重要な問題は、バージョン チェーン内のどのバージョンが現在のトランザクションに表示されるかを判断することです。これは、ReadView によって解決される主な問題です。
This ReadView主に 4 つが含まれます。 より重要な内容は次のとおりです。
この ReadView では、特定のレコードにアクセスするときに、レコードの特定のバージョンが表示されているかどうかを確認するには、以下の手順に従ってください。
アクセスされたバージョンの trx_id 属性値が creator_trx_id
値と同じ場合ReadView では、現在のトランザクションが独自の変更されたレコードにアクセスしているため、このバージョンは現在のトランザクションからアクセスできることを意味します。 ReadView ## の
値以上の場合は、このバージョンを生成したトランザクションが現在のトランザクションが ReadView を生成した後に開かれたことを示します。現在のトランザクションではこのバージョンにアクセスできません。
と low_limit_id
の間にある場合、 trx_id 属性値が
これらの概念を理解した後、レコードをクエリするときにシステムが MVCC を通じてレコードをどのように見つけるかを見てみましょう:
2。 GetReadView;
3.取得したデータをクエリし、ReadView のトランザクション バージョン番号と比較します。
4。Readview ルールを満たさない場合は、Undo ログから履歴スナップショットを取得する必要があります。 ;
5.最終的に、ルールに準拠したデータが返されます。
データの特定のバージョンが現在のトランザクションに表示されない場合は、バージョン チェーンに従ってデータの次のバージョンを見つけ、引き続き上記の手順に従って可視性を確認します。など、バージョン チェーンの最後のバージョンまで続きます。最新バージョンが表示されない場合、レコードはトランザクションに表示されず、クエリ結果にも含まれません。
分離レベルが読み取りコミットの場合、トランザクション内の各選択クエリは読み取りビューを再取得します。
は次のとおりです:
5. 例次のように仮定します。 トランザクション ID
が8 である
トランザクションによって Student テーブルに挿入されたレコードは 1 つだけです:MVCC は、READ COMMITTED と REPEATABLE READ の 2 つの分離レベルでのみ動作します。次に、
READ COMMITTEDとREPEATABLE READ
5.1 READ COMMITTED
分離レベルの下:
現在、トランザクション ID 10 と 20 の 2 つのトランザクションが実行されています:
説明: トランザクション実行中プロセスでは、レコードが初めて実際に変更されたとき (INSERT、DELETE、UPDATE ステートメントの使用など) にのみ、別のトランザクション ID が割り当てられ、このトランザクション ID がインクリメントされます。そのため、トランザクション ID を割り当てられるようにするために、トランザクション 2 で他のテーブルのいくつかのレコードを更新しました。
現時点で、student テーブルの ID 1 のレコードによって取得されるバージョン リンク リストは次のとおりです。
ここで、READ COMMITED 分離レベルを使用するトランザクションが実行を開始すると仮定します。
この SELECT1 の実行プロセスは次のとおりです。
ステップ 1: SELECT ステートメントを実行すると、最初に ReadView
が生成されます。ReadView の trx_ids
リストの内容は [ 10, 20]、up_limit_id
は 10
、low_limit_id
は 21
、creator_trx_id
は 0
。
ステップ 2: バージョン チェーンから表示可能なレコードを選択します。図からわかるように、最新バージョンの列 name
の内容は '王五'
です。 ##trx_id の値は
10 であり、これは trx_ids リスト内にあるため、可視性の要件を満たしておらず、roll_pointer に従って次のバージョンにジャンプします。
ステップ 3: 次のバージョンの列
name の内容は
'李思' であり、このバージョンの
trx_id 値も # です##10
は trx_ids
リストにも含まれているため、要件を満たしておらず、引き続き次のバージョンにジャンプします。 ステップ 4: 次のバージョンの列
name
の内容は ‘Zhang San'
で、このバージョンの trx_id
値は 8
、ReadView の up_limit_id 値 10 より小さいため、このバージョンは要件を満たしています。ユーザーに返されるバージョンは、列名が「Zhang San」であるレコードです。 その後、トランザクション
を 10
:
に送信し、# # に進みます。 #トランザクション ID
は20 テーブル
student のレコードを更新します (
id は
1:
#)
##現時点では、student テーブルの ID 1 のレコードのバージョン チェーンは次のようになります:
次に、READ COMMITTED 分離レベルを使用したトランザクション内で ID 1 のレコードの検索を続けます。
は次のとおりです:
この SELECT2 の実行 処理は次のとおりです:
ステップ 1: SELECT ステートメントを実行すると、ReadView が別途生成されます ReadView の trx_ids リストの内容は [20]、up_limit_id は 20、low_limit_id は 21、creator_trx_id は 0 です。
ステップ 2:バージョン チェーンから表示されているレコードを選択します。図からわかるように、最新バージョンの列名の内容は「Song Ba」であり、この tr_id 値はバージョンは 20 で、trx_ids リストに含まれているため、可視性の要件を満たしておらず、roll.pointer に従って次のバージョンにジャンプします。
ステップ 3:次のバージョンの列名の内容は「Qian Qi」です。このバージョンの trx_id 値は 20 であり、これは trx_ids リストにも含まれているため、一致しません要件を満たしてから、次のバージョンにジャンプしてください。 ステップ 4: 次のバージョンの列名の内容は「王五」です。このバージョンの trx_id 値は 10 であり、ReadView の up_limit.id 値 20 より小さいため、このバージョンは要件を満たしています。 、そして最後にユーザーに与えられるバージョンは、列名が「王五」であるレコードです。
同様に、トランザクション ID 20 のレコードも後で送信された場合、テーブル Student の ID 値 1 のレコードが READ CONMMITTED 分離レベルを使用してトランザクションで再度クエリされると、得られる結果は「Song Ba」になります。特定のプロセスについては分析しません。 5.2 REPEATABLE READ
REPEATABLE READ 分離レベルを使用するトランザクションの場合、クエリ ステートメントは初回 ReadView が生成されると、後続のクエリは繰り返し生成されません。
たとえば、システム内でトランザクション ID 10 と 20 の 2 つのトランザクションが実行されているとします。此刻,表student中id为1的记录得到的版本链表如下所示:
假设现在有一个使用REPEATABLE READ隔离级别的事务开始执行:
此时执行过程与read committed相同
然后再到刚才使用REPEATABLE READ隔离级别的事务中继续查找id为1的记录,如下:
这个SELECT2的执行过程如下:
步骤1:因为当前事务的隔离级别为REPEATABLE READ,而之前在执行SELECT1时已经生成过ReadView了,所以此时直接复用之前的ReadView,之前的ReadView的trx_ids列表的内容就是[10,20],up_limit_id为10, low_limit_id为21 , creator_trx_id为0。
步骤2:然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列name的内容是’宋八’trx_id值为20,在trx_ids列表内,所以不符合可见性要求,根据roll_pointer跳到下一个版本。
步骤3:下一个版本的列name的内容是’钱七’,该版本的trx_id值为20,也在trx_ids列表内合要求,继续跳到下一个版本。
步骤4:下一个版本的列name的内容是’王五’,该版本的trx_id值为10,而trx_ids列表中是包含值为10的事务id的,所以该版本也不符合要求,同理下一个列name的内容是’李四’的版本也不符合要求。继续跳到下个版本。
步聚5∶下一个版本的列name的内容是’张三’,该版本的trx_id值为80,小于Readview中的up_limit_id值10,所以这个版本是符合要求的,最后返回给用户的版本就是这条列c为‘张三’的记录。
两次SELECT查询得到的结果是重复的,记录的列c值都是’张三’,这就是可重复读的含义。如果我们之后再把事务id为20的记录提交了,然后再到刚才使用REPEATABLE READ隔离级刷的事务中继续查找这个id为1的记录,得到的结果还是’张三’,具体执行过程大家可以自己分析一下。
假设现在表student中只有一条数据,数据内容中,主键id=1,隐藏的trx_id=10,它的undo log如下图所示。
假设现在有事务A和事务B并发执行,事务A的事务id为20,事务B的事务id为30。
步骤1:事务A开始第一次查询数据,查询的SQL语句如下。
select * from student where id > 1;
在开始查询之前,MySQL会为事务A产生一个ReadView,此时ReadView的内容如下: trx_ids=[20, 30 ] ,up_limit_id=20 , low_limit_id=31 , creator_trx_id=20。
因为表student只有一条符合条件 where id>=1 的数据,所以会被查询出来。然后根据ReadView机制,发现该行数据的trx_id=10,小于事务A的ReadView里up_limit_id,这表示这条数据是事务A开启之前,其他事务就已经提交了的数据,因此事务A可以读取到。
结论:事务A的第一次查询,能读取到一条数据,id=1。
步骤2∶接着事务B(trx_id=30),往表student中新插入两条数据,并提交事务。
insert into student(id,name) values(2,'李四'); insert into student(id,name) values(3,'王五');
此时表student中就有三条数据了,对应的undo如下图所示:
ステップ 3: 次に、トランザクション A が 2 番目のクエリを開始します。反復読み取り分離レベルのルールに従って、トランザクション A はこの時点では ReadView を再生成しません。このとき、studentテーブルの3つのデータは、いずれもwhere id>=1の条件を満たすため、最初に検索されます。次に、ReadView の仕組みに従って、トランザクション A で各データが閲覧できるかどうかを判断します。
1) まず、前述の通り、id=1 のデータはトランザクション A から見ることができます。
2) 次に、id=2、trx_id=30 のデータがあります。このとき、トランザクション A は、この値が up_limit_id と low_limit_id の間にあることを検出するため、trx_ids 配列に 30 があるかどうかを判断する必要があります。 。配列ではトランザクション A の trx_ids=[20,30] となっているため、id=2 のデータはトランザクション A と同時に開始された他のトランザクションによって送信されたことを意味するため、このデータはトランザクション A からは見ることができません。
3) 同様に、id=3 のこのデータの trx_id も 30 であるため、トランザクション A からは見ることができません。
#結論:トランザクション A の 2 番目のクエリは、id=1 のデータのみをクエリできます。これはトランザクション A の最初のクエリの結果と同じであるため、ファントム リード現象は発生せず、MySQL の反復可能な分離レベルではファントム リードの問題は発生しません。
以上がMySQL マルチバージョン同時実行制御 MVCC インスタンス分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。