2022年です。MVCCを理解していないとイライラしすぎます

私は以前に記事を書き、MySQLにはナレッジポイントMVCCマルチバージョン同時実行制御が含まれていることを共有しました。私はこの問題を理解していません、私はいつも何かが欠けていると感じています。それで、今日はMVCCについてお話ししたいと思います。

MVCCを理解するには、最初にInnoDBのトランザクションの分離レベルを理解するのが最善です。そうでないと、概念だけを見てMVCCを理解することは困難です。

1.分離レベル

1.1理論

MySQL中間トランザクションには、次の4つの分離レベルがあります。

  • シリアル化(SERIALIZABLE)
  • 繰り返し読む
  • コミット済みを読む
  • コミットされていない読み取り

4つの異なる分離レベルには、次の意味があります。

  1. シリアル化可能

分離レベルがシリアル化されている場合、現在のトランザクションはユーザー間で次々に実行され、この分離レベルはトランザクション間の最大の分離を提供します。

  1. 繰り返し読む

この分離レベルでの繰り返し可能な読み取りでは、トランザクションはシーケンスとは見なされません。ただし、現在実行中のトランザクションの変更を外部から確認することはできません。つまり、ユーザーが別のトランザクションで同じSELECTステートメントを複数回実行した場合、結果は常に同じになります。(実行中のトランザクションによって生成されたデータの変更は外部から見ることができないため)。

  1. コミット済みを読む

READ COMMITTED分離レベルは、REPEATABLEREAD分離レベルよりも安全性が低くなります。READ COMMITTEDレベルのトランザクションは、他のトランザクションによるデータ変更を確認できます。つまり、トランザクション処理中に、他のトランザクションが対応するテーブルを変更した場合、同じトランザクションの複数のSELECTステートメントが異なる結果を返す可能性があります。

  1. コミットされていない読み取り

READ UNCOMMITTEDは、トランザクション間の分離を最小限に抑えます。この分離レベルのトランザクションは、ファントム読み取り操作と繰り返し不可能な読み取り操作を簡単に生成できるだけでなく、他のトランザクションがまだコミットしていないデータを読み取ることができます。コミットされた変更は親トランザクションによって取り消されるため、大量のデータ変更が発生します。

MySQLデータベースでは、デフォルトのトランザクション分離レベルはREPEATABLEREADです。

1.2SQLプラクティス

次に、上記の理論は、いくつかの簡単なメッセージSQLを介して読者に検証されます。

1.2.1分離レベルを表示する

データベースインスタンスのデフォルトのグローバル分離レベルと現在のセッションの分離レベルは、次のSQLで表示できます。

MySQL8より前では、次のコマンドを使用してMySQL分離レベルを表示します。

SELECT @@GLOBAL.tx_isolation, @@tx_isolation;
复制代码

クエリ結果を次の図に示します。

ご覧のとおり、デフォルトの分離レベルはREPEATABLE-READであり、グローバル分離レベルと現在のセッション分離レベルの両方です。

MySQL8以降、次のコマンドを使用してMySQLのデフォルトの分離レベルを確認します

SELECT @@GLOBAL.transaction_isolation, @@transaction_isolation;
复制代码

キーワードが変更されただけで、他はすべて同じです。

分離レベルは、次のコマンドで変更できます(開発者は、グローバル分離レベルを変更せずに、変更時に現在のセッション分離レベルを変更することをお勧めします)。

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
复制代码

上記のSQLは、現在のセッションのデータベース分離レベルをREAD UNCOMMITTEDに設定することを意味します。設定が成功したら、図1-2に示すように、分離レベルを再度クエリして、現在のセッションの分離レベルが変更されていることを確認します。

現在のセッションの分離レベルのみを変更した場合、セッションを変更した後、分離レベルはデフォルトの分離レベルに復元されるため、テスト時に現在のセッションの分離レベルを変更できることに注意してください。

1.2.2コミットされていない読み取り

1.2.2.1テストデータの準備

READ UNCOMMITTEDは、最も低い分離レベルです。この分離レベルには、ダーティリード、繰り返し不可能な読み取り、およびファントム読み取りが存在するため、最初にこの分離レベルを見て、これら3つの問題が何であるかを理解できるようにします。

それらは以下で別々に紹介されます。

まず、次のように2つのデータを事前設定して、簡単なテーブルを作成します。

表面上のデータは非常に単純です。javaboyとitboyhubの2人のユーザーがいて、それぞれのアカウントには1,000元があります。次に、これら2人のユーザー間の転送操作をシミュレートします。

リーダーがNavicatを使用して真実を伝える場合、異なるクエリウィンドウは異なるセッションに対応します。リーダーがSQLyogを使用する場合、異なるクエリウィンドウは同じセッションに対応するため、SQLyogを使用する場合は、新しい接続を開く必要があります。新しい接続でクエリ操作を実行します。

1.2.2.2ダーティリード

トランザクションが別のトランザクションによってコミットされていないデータを読み取る場合、それはダーティリードと呼ばれます。具体的な操作は次のとおりです。

  1. まず、AとBをそれぞれ想定して2つのSQL操作ウィンドウを開き、Aウィンドウに次のSQLを入力します(入力が完了した後は実行しないでください)。
START TRANSACTION;
UPDATE account set balance=balance+100 where name='javaboy';
UPDATE account set balance=balance-100 where name='itboyhub';
COMMIT;
复制代码
  1. 次のように、Bウィンドウで次のSQLを実行し、デフォルトのトランザクション分離レベルをREADUNCOMMITTEDに変更します。
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
复制代码
  1. 次に、Bウィンドウに次のSQLを入力します。入力が完了したら、最初に最初の行を実行してトランザクションを開きます(1行だけを実行する必要があることに注意してください)。
START TRANSACTION;
SELECT * from account;
COMMIT;
复制代码
  1. 次に、Aウィンドウで最初の2つのSQLを実行します。つまり、トランザクションを開き、javaboyアカウントに100元を追加します。
  2. Bウィンドウに入り、Bウィンドウで2番目のクエリSQL(SELECT * from user;)を実行します。結果は次のとおりです。

Aウィンドウのトランザクションは送信されていませんが、データの関連する変更をBウィンドウで照会できることがわかります。

これはダーティリードの問題です。

1.2.2.3繰り返し不可の読み取り

繰り返し不可の読み取りとは、トランザクションが同じレコードを連続して読み取るが、2回読み取られるデータが異なることを意味します。これは、繰り返し不可の読み取りと呼ばれます。具体的な操作手順は次のとおりです(操作の前に、両方のアカウントのお金を1000に戻します)。

  1. 最初に2つのクエリウィンドウAとBを開き、Bのデータベーストランザクション分離レベルをREADUNCOMMITTEDに設定します。特定のSQLについては、上記を参照してください。ここでは詳しく説明しません。
  2. Bウィンドウに次のSQLを入力し、最初の2つのSQLのみを実行してトランザクションを開き、javaboyのアカウントを照会します。
START TRANSACTION;
SELECT * from account where name='javaboy';
COMMIT;
复制代码

最初の2つのSQL実行結果は次のとおりです。

  1. 次のように、ウィンドウAで次のSQLを実行して、javaboyアカウントに100元を追加します。
START TRANSACTION;
UPDATE account set balance=balance+100 where name='javaboy';
COMMIT;
复制代码

4.再度Bウィンドウに戻り、Bウィンドウの2番目のSQLを実行してjavaboyアカウントを確認します。結果は次のとおりです。

javaboyアカウントが変更されました。つまり、javaboyアカウントが前後に2回チェックされ、結果に一貫性がなく、繰り返し読み取りができません。

ダーティ読み取りとの違いは、ダーティ読み取りでは他のトランザクションによってコミットされていないデータが表示されるのに対し、繰り返し不可の読み取りでは他のトランザクションによってコミットされたデータが表示されることです(現在のSQLもトランザクション内にあるため、他のトランザクションを見たくない。データはすでに送信されている)。

1.2.2.4ファントムリード

幻覚的な読書は繰り返し不可能な読書と非常に似ており、名前を見ることは幻覚です。

簡単な例を挙げます。

Aウィンドウに次のSQLを入力します。

START TRANSACTION;
insert into account(name,balance) values('zhangsan',1000);
COMMIT;
复制代码

次に、Bウィンドウに次のSQLを入力します。

START TRANSACTION;
SELECT * from account;
delete from account where name='zhangsan';
COMMIT;
复制代码

次の手順を実行します。

  1. 最初にBウィンドウの最初の2行を実行し、トランザクションを開いて、データベース内のデータを同時にクエリします。現時点では、クエリされるデータはjavaboyとitboyhubのみです。
  2. ウィンドウAの最初の2行を実行し、zhangsanという名前のユーザーをデータベースに追加します。トランザクションをコミットしないように注意してください。
  3. ウィンドウBの2行目を実行します。ダーティリードの問題のため、この時点でユーザーzhangsanにクエリを実行できます。
  4. ウィンドウBの3行目を実行して、zhangsanという名前のレコードを削除します。このとき、削除に問題があります。ウィンドウBでzhangsanを照会できますが、このレコードは読み取りのために読み取られたため、送信されていません。汚れた読み物です。そこにあるので削除できません。この時、幻覚が発生し、明らかに張山がありましたが、削除できませんでした。

これは幻の読書です。

上記のケースを読んだ後、ダーティリーディング繰り返し不可能なリーディングファントムリーディングの意味を理解する必要があります

1.2.3READCOMMITTED

READ UNCOMMITTEDと比較すると、READ COMMITTEDは主にダーティリードの問題を解決しますが、繰り返し不可能なリードやファントムリードの問題は解決しません。

トランザクションの分離レベルをREADCOMMITTEDに変更した後、ダーティ読み取りの場合に上記のテストを繰り返し、ダーティ読み取りの問題がないことを確認します。繰り返し不可能な読み取りの場合に上記のテストを繰り返し、繰り返し可能な読み取りの問題はまだ存在します。

上記のケースはファントムリーディングテストには当てはまりません。ファントムリーディングテストケースを変更してみましょう。

または、2つのウィンドウAとBの場合、Bウィンドウの分離レベルをREADCOMMITTEDに変更します。

次に、Aウィンドウに次のテストSQLを入力します。

START TRANSACTION;
insert into account(name,balance) values('zhangsan',1000);
COMMIT;
复制代码

Bウィンドウに次のテストSQLを入力します。

START TRANSACTION;
SELECT * from account;
insert into account(name,balance) values('zhangsan',1000);
COMMIT;
复制代码

試験方法は次のとおりです。

  1. まず、BウィンドウでSQLの最初の2行を実行し、トランザクションを開いてデータをクエリします。現時点では、javaboyとitboyhubの2人のユーザーのみが見つかります。
  2. ウィンドウAでSQLの最初の2行を実行し、レコードを挿入しますが、トランザクションはコミットしません。
  3. ウィンドウBでSQLの2行目を実行します。ダーティリードの問題がないため、現時点ではウィンドウAに追加されたデータが見つかりません。
  4. BウィンドウでSQLの3行目を実行します。名前フィールドは一意であるため、ここに挿入することはできません。この時点で幻覚が発生します。明らかにzhangsanユーザーは存在しませんが、zhangsanを挿入することはできません。

1.2.4繰り返し読み取り

READ COMMITTEDと比較して、REPEATABLE READは、繰り返し不可能な読み取りの問題をさらに解決しますが、ファントム読み取りは解決しません。

REPEATABLE READでのファントム読み取りに関するテストは、基本的に前のセクションと同じですが、2番目のステップで挿入SQLを実行した後、トランザクションをコミットすることを忘れないでください。

REPEATABLE READは繰り返し不可能な読み取りを解決したため、2番目のステップでトランザクションがコミットされても、3番目のステップでコミットされたデータが見つからず、4番目のステップが挿入を続けるとエラーが発生します。

REPEATABLE READは、InnoDBエンジンのデフォルトのデータベーストランザクション分離レベルでもあることに注意してください。

1.2.5シリアル化可能

SERIALIZABLEは、トランザクション間の最大の分離を提供します。この分離レベルでは、トランザクションは、ダーティ読み取り、繰り返し不可能な読み取り、および最も安全なファントム読み取りの問題なしに、次々に実行されます。

現在のトランザクション分離レベルがSERIALIZABLEに設定されている場合、この時点で他のトランザクションが開かれるとブロックされ、他のトランザクションが正常に開かれる前に、他のトランザクションは現在のトランザクションがコミットされるまで待機する必要があるため、以前のダーティの問題読み取り、繰り返し不可能な読み取り、およびファントム読み取りはここにはありません。発生します。

1.3まとめ

一般に、分離レベルとダーティリード、繰り返し不可のリード、およびファントムリードの対応は次のとおりです。

分離レベル

ダーティリード

繰り返し不可の読み取り

ファントムリーディング

コミットされていない読み取り

許可する

許可する

許可する

コミット済みを読む

禁止されている

許可する

許可する

繰り返し読む

禁止されている

禁止されている

許可する

シリアル化可能

禁止されている

禁止されている

禁止されている

パフォーマンスの関係を図に示します。

Song Geは、少し前に分離レベルのビデオも録画しました。以下を参照してください。

  • www.bilibili.com/video/BV14L…

2.スナップショットの読み取りと現在の読み取り

次に、問題を把握する必要があります。スナップショットの読み取りと現在の読み取りです。

2.1スナップショットの読み取り

SnapShot Readは、一貫性のあるロック解除された読み取りであり、これがInnoDBストレージエンジンの同時実行性が高い主な理由の1つです。

反復可能読み取りの分離レベルでは、トランザクションが開始されると、現在のライブラリの写真(スナップショット)が取得されます。スナップショット読み取りによって読み取られるデータは、写真が撮影されたときのデータ、またはによって挿入/変更されたデータのいずれかです。現在のトランザクション自体。データ。

この記事の最初のセクションに含まれるすべてのクエリを含め、私たちが毎日使用するロック解除されたクエリはスナップショット読み取りであり、これについては説明しません。

2.2現在の読み

スナップショット読み取りに対応するのは現在の読み取りです。現在の読み取りは、履歴バージョンのデータではなく、最新のデータを読み取ることです。つまり、反復可能な読み取り分離レベルでは、現在の読み取りを使用すると、読み取りも可能になります。他のトランザクションがコミットされていること。データ。

SongGeが例を示します。

MySQLトランザクションは2つのセッションAとBを開始します。

最初にセッションAでトランザクションを開始し、ID1のレコードを照会します。

次に、次のように、BセッションでIDが1のデータを変更します。

Bセッションはトランザクションを開いたり、タイムリーなコミットトランザクションを開いたりしないことに注意してください。そうしないと、updateステートメントが排他ロックを占有し、Aセッションでロックがしばらく使用されるとブロックが発生します。

次に、セッションAに戻り、次のようにクエリを続行します。

セッションAの最初のクエリはスナップショット読み取りであり、現在のトランザクションが開かれたときにデータステータスを読み取り、最後の2つのクエリは現在の読み取りであり、最新のデータを読み取ります(セッションBでの変更後のデータ) 。

3.ログを元に戻す

元に戻るログをもう一度見てみましょう。これは、後続のMVCCを理解するのにも役立ちます。ここでは、簡単に紹介します。

データベーストランザクションにはロールバック機能があることがわかっています。ロールバックできるため、データを変更する前に古いデータを記録する必要があります。これは、将来のロールバックの基礎として、このレコードが取り消しログになります。

レコードを追加する場合は、追加したデータIDをUNDOログに記録し、今後ロールバックする場合はそれに応じてデータを削除します。データを削除または変更する場合は、元のデータをUNDOログに記録し、データを復元します。したがって、将来的に。クエリ操作にはロールバック操作が含まれないため、取り消しログに記録する必要はありません。

4.ラインフォーマット

次に、MVCCを理解するのにも役立つラインフォーマットを見てみましょう。

行形式は、InnoDBが各行のデータを保存するときに各行のデータを保存する形式です。

データベースには、COMPACT、REDUNDANT、DYNAMIC、COMPRESSEDなど、いくつかの行形式があります。ただし、どの行形式であっても、次の非表示のデータ列は避けられません。

上の図の列1、列2、列3から列Nまでは、データベース内のテーブルの列であり、通常のデータを格納します。データを保存するこれらの列に加えて、3つの追加のデータ列があります。追加されました。これは、DB_ROW_ID、DB_TRX_ID、DB_ROLL_PTRの3つの列でもあり、ここで注目する必要があります。

  • DB_ROW_ID:この列は6バイトを占め、データの行を一意に識別するために使用される行IDです。ユーザーがテーブルの作成時に主キーを設定しない場合、システムはこの列に基づいて主キーインデックスを作成します。
  • DB_TRX_ID:この列は6バイトを占め、トランザクションIDです。InnoDBストレージエンジンでは、トランザクションを開くときに、トランザクションIDをInnoDBトランザクションシステムに適用します。このトランザクションIDは、厳密に増加する一意の番号です。現在のデータ行は、どのトランザクションによって変更されますか。 be現在の行に対応するトランザクションIDを記録します。
  • DB_ROLL_PTR:この列は7バイトを占め、ロールバックポインターです。このロールバックポインターは、レコードを以前のバージョンに復元できる元に戻すログログのアドレスを指します。

了解しました。データ行の形式について少し説明します。

5.MVCC

前のセクションの準備知識を持って、MVCCを正式に見てみましょう。

MVCC、英語でのフルネームはマルチバージョン同時実行制御であり、マルチバージョン同時実行制御として中国語に翻訳されています。

MVCCの中心的なアイデアは、データ行の履歴バージョンを保存し、データ行の複数のバージョンを管理することでデータベースの同時実行制御を実現することです。

簡単に言うと、私たちが通常目にするレコードがデータベースに保存されている場合、1つのレコードだけでなく、複数の履歴バージョンが存在する可能性があります。

以下に示すように:

この絵はよく理解されており、皆さんのMVCCはあまり理解されていないと思います。

次に、この画像をさまざまな分離レベルと組み合わせて説明します。

5.1繰り返し可能な読み取り

まず、INSERT \ DELETE \ UPDATEを使用してデータの行を操作すると、トランザクションIDが生成され、このトランザクションIDも行レコード(DB_TRX_ID)に格納されます。つまり、このトランザクションは現在のデータ行を変更します。その後、記録されます。

INSERT \ DELETE \ UPDATE操作により、対応するUNDOログログが生成されます。レコードの各行には、UNDOログログを指すDB_ROLL_PTRがあります。レコードの各行について、UNDOログログを実行することにより、前のレコードに復元できます。記録、前の記録事前記録...

トランザクションを開くとき、最初にトランザクションIDをInnoDBのトランザクションシステムに適用します。このIDは厳密に増加する数値です。現在のトランザクションが開かれる時点で、システムは配列を作成し、配列はすべてを保存します。現在アクティブなトランザクションID、いわゆるアクティブトランザクションとは、開かれているがまだコミットされていないトランザクションを指します。

この配列の最小値は理解しやすいです。一部の友人は、配列の最大値が現在のトランザクションのIDであると誤解する可能性があります。実際、これは必ずしもそうではなく、それよりも大きい場合があります。trx_idの適用からアレイの作成までに時間がかかるため、この期間中に他のセッションもtrx_idに適用される場合があります。

現在のトランザクションがデータの行を表示する場合、最初にデータの行のDB_TRX_IDをチェックします。

  1. この値が現在のトランザクションIDと等しい場合は、これが現在のトランザクションによって変更され、データが表示されていることを意味します。
  2. この値が配列の最小値よりも小さい場合は、現在のトランザクションを開始したときに、この行のデータ変更に関連するトランザクションがコミットされ、現在のデータ行が表示されていることを意味します。
  3. この値が配列の最大値より大きい場合、このデータ行はトランザクションを開いた後ですが、コミットする前に、別のセッションもトランザクションを開いてこのデータ行を変更することを意味します。このデータ行は次のようになります。見えない。
  4. この値のサイズが配列の最大値と最小値の間にあり(閉範囲)、値が配列にない場合、これはコミットされたトランザクションによって変更されたデータでもあり、表示されていることを意味します。
  5. この値のサイズが配列内の最大値と最小値の間にあり(閉範囲)、値が配列内にある(現在のトランザクションIDと等しくない)場合、これはによって変更されたデータであることを意味しますコミットされていないトランザクションであり、表示されません。

最初の3つの状況、主に後者の2つの状況をよく理解する必要があります。SongGeは簡単な例を示しています。

たとえば、4つのセッションA、B、C、およびDがあります。最初に、A、B、およびCがそれぞれトランザクションを開始します。トランザクションIDは3、4、および5です。次にセッションCはトランザクションをコミットしますが、AとBはしません。次に、Dセッションもトランザクションを開始します。トランザクションIDは6です。次に、Dセッションがトランザクションを開始すると、配列の値は[3,4,6]になります。ここで、DB_TRX_IDが5のデータの行があると仮定し(4番目のケース)、データの行が表示されます(現在のトランザクションが開かれたときにコミットされているため)。DB_TRX_IDが4のデータの行がある場合、その場合、行は表示されません(コミットされていないため)。

もう1つの注意点は、データ更新操作が現在のトランザクションに含まれている場合、更新操作はスナップショットの読み取りではなく、現在の読み取りに基づいて更新されることです。後者の場合、データが失われる可能性があります。結果。

次の表を想定して、例を挙げましょう。

これで、2つのセッションAとBがあり、最初にAでトランザクションを開始します。

次に、セッションBで変更操作を実行します(トランザクションを明示的に開かずに、トランザクションは更新SQL内で開かれ、更新が完了した後、トランザクションは自動的にコミットされます)。

次に、セッションAに戻り、レコードをクエリして、値が期待どおりに変更されていないことを確認し(現在、分離レベルは繰り返し読み取り可能です)、Aで変更操作を実行し、変更が完了した後に次のようにクエリを実行します。下に示された:

実際には100を基準に更新されているのがわかりやすいのですが、99を基準に更新すると100の更新が失われるのは明らかですが、これは間違いです。

実際、MySQLの更新では、最初に読み取りを行ってから更新します。読み取る場合、デフォルトは現在の読み取りです。つまり、ロックされます。したがって、上記の場合、トランザクションがBセッションで明示的に開かれ、コミットされていないものがない場合、Aセッションの更新ステートメントはブロックされます。

これはMVCCであり、単一のレコードには複数のバージョンがあります。読み取り/書き込み同時実行制御を実現し、読み取りと書き込みが相互にブロックしないと同時に、MVCCで楽観的ロックが採用され、データの読み取りはロックされず、データの書き込みは行のみをロックするため、デッドロックの可能性が低くなります。それに応じてスナップショットの読み取りも実現できます。

5.2コミット済みの読み取り

READCOMMITTEDはREPEATABLEREADに似ていますが、主な違いは、後者が各トランザクションの開始時に一貫したビューを作成する(アクティブなトランザクションIDを一覧表示する配列を作成する)のに対し、前者は各ステートメントが実行される前に新しいビューを再計算することです。

したがって、READ COMMITTED分離レベルでは、他のセッションがコミットしたデータが表示されます(他のセッションが現在のセッションよりも遅く開かれた場合でも)。

6.まとめ

MVCCは、ある程度の読み取りと書き込みの同時実行性を実現しますが、READCOMMITTEDとREPEATABLEREADの2つの分離レベルでのみ有効です。

READ UNCOMMITTEDは常に最新のデータ行を読み取りますが、SERIALIZABLEはすべての読み取り行をロックします。どちらもMVCCと互換性がありません。

わかりました。ご理解いただけるかわかりませんが、ご不明な点がございましたら、メッセージを残してご相談ください。


著者:江南の小さな雨
リンク:https://juejin.cn/post/7044043884694863902
出典:希土類ナゲット
著作権は著者に帰属します。商用の再版については、著者に連絡して許可を求め、非商用の再版については、出典を示してください。

おすすめ

転載: blog.csdn.net/wdjnb/article/details/124363522