MySQLにおけるトランザクション分離レベルの実装原理の詳細な説明

MySQLにおけるトランザクション分離レベルの実装原理の詳細な説明

序文

データベース トランザクションに関して言えば、トランザクションの ACID 特性、分離レベル、解決される問題 (ダーティ リード、非反復読み取り、ファントム リード) など、トランザクションに関連する多くの知識が誰の頭にもすぐに浮かびますが、これらのトランザクション特性がどのように実装されているか、また 4 つの分離レベルがなぜあるのかを実際に理解している人はほとんどいないでしょう。

今日はまず、MySQL におけるトランザクション分離の実装原理について説明し、他の機能の実装原理を分析する記事を引き続き公開していきます。

もちろん、MySQL は広範かつ奥深いため、この記事で省略される部分があることは避けられません。批判や訂正は大歓迎です。

例示する

MySQL のトランザクション実装ロジックはエンジン層にあり、すべてのエンジンがトランザクションをサポートしているわけではありません。次の手順は InnoDB エンジンに基づいています。

意味

分離とは、異なるトランザクションが次々に送信され、実行された後、最終結果がシリアルになることを意味します。つまり、トランザクションの場合、実行中に認識されるデータの変更は、そのトランザクション自体の操作によって発生したものだけであり、他のトランザクションによって発生したデータの変更は発生しないはずです。

分離により、同時トランザクションから発生する問題が解決されます。

標準SQL分離レベル

分離を実装する最も簡単な方法は、各トランザクションを順番に実行することです。前のトランザクションが完了していない場合、後続のトランザクションは待機します。しかし、この実装方法は明らかに並行処理効率が低く、実際の環境での使用には適していません。

上記の問題を解決し、さまざまなレベルの同時実行制御を実現するために、SQL 標準の設定者は、コミットされていない読み取り、コミットされた読み取り、繰り返し読み取り、およびシリアル化可能な読み取りという異なる分離レベルを提案しました。最も高い分離レベルはシリアル読み取りです。他の分離レベルでは、トランザクションが同時に実行されるため、多少の問題が許容されます。以下のマトリックスを参照してください。

分離レベル (+: 許可、-: 許可されない)ダーティリード繰り返し不可能な読み取りファントムリード
コミットされていない読み取り+ + +
コミット読み取り- + +
繰り返し読み取り- - +
シリアル読み取り- - -

MySQL の InnoDB エンジンは、コミット読み取りレベルで MVCC を通じて非反復読み取りの問題を解決し、繰り返し読み取りレベルでギャップ ロックを通じてファントム読み取りの問題を解決します。詳細については、以下の分析を参照してください。

実施原則

標準SQLトランザクション分離レベルの実装原則

上記で遭遇した問題は、実際には同時トランザクションにおける制御の問題です。同時トランザクションを解決する最も一般的な方法は、悲観的同時実行制御 (つまり、データベースのロック) です。標準 SQL トランザクション分離レベルの実装はロックに依存します。どのように実装されるかを見てみましょう。

トランザクション分離レベル実装
コミットされていない読み取り (RU)トランザクションは現在読み取られているデータをロックしません。

トランザクションが特定のデータを更新するとき (つまり、更新が発生した瞬間)、最初に行レベルの共有ロックを追加する必要があり、このロックはトランザクションが終了するまで解放されません。
読み取りコミット (RC)トランザクションは、現在読み取られているデータに行レベルの共有ロックを追加し (読み取り時にのみロックされます)、行を読み取った直後に行レベルの共有ロックを解除します。

トランザクションが特定のデータを更新するとき (つまり、更新が発生した瞬間)、最初に行レベルの排他ロックを追加する必要があり、トランザクションが終了するまで解放されません。
繰り返し読み取り (RR)トランザクションがデータを読み取るとき (つまり、読み取りを開始した瞬間)、最初に行レベルの共有ロックを追加する必要があります。このロックは、トランザクションが終了するまで解放されません。

トランザクションが特定のデータを更新するとき (つまり、更新が発生した瞬間)、最初に行レベルの排他ロックを追加する必要があり、トランザクションが終了するまで解放されません。
シリアル読み取り (S)トランザクションがデータを読み取るときは、まずテーブル レベルの共有ロックを追加する必要があります。このロックは、トランザクションが終了するまで解放されません。

トランザクションがデータを更新する場合、最初にテーブル レベルの排他ロックを追加する必要があります。このロックは、トランザクションが終了するまで解放されません。

ロックのみを使用して分離レベル制御を実装すると、頻繁なロックとロック解除が必要になり、読み取り/書き込みの競合が発生する可能性が高くなることがわかります (たとえば、RC レベルでは、トランザクション A がデータ行 1 を更新し、トランザクション B はトランザクション A がコミットする前にデータ行 1 を読み取り、トランザクション A がコミットしてロックを解除するまで待機する必要があります)。

ロックなしで読み取り/書き込み競合の問題を解決するために、MySQL は MVCC メカニズムを導入しました。詳細については、以前の分析記事「データベースの楽観的ロック、悲観的ロック、および MVCC を 1 つの記事で理解する」を参照してください。

InnoDB トランザクション分離レベルの実装原則

先に進む前に、いくつかの概念を理解する必要があります。

1. ロック読み取りと一貫性のある非ロック読み取り

読み取りのロック: SELECT ... LOCK IN SHARE MODE や SELECT ... FOR UPDATE などのトランザクションで読み取りをアクティブにロックします。行共有ロックと行排他ロックがそれぞれ追加されます。ロックの分類については、以前の分析記事「知っておくべき MySQL ロックの分類」で確認できます。

ロックされた読み取り

一貫性のある非ロック読み取り: InnoDB は MVCC を使用して、トランザクションのクエリに対して特定の時点でのデータベース スナップショットを提供します。クエリでは、その時点より前にコミットされたトランザクションによって行われた変更は参照されますが、それ以降のトランザクションまたはコミットされていないトランザクションによって行われた変更は参照されません (このトランザクションを除く)。つまり、トランザクションが開始された後、トランザクションによって表示されるデータは、トランザクションが開始された瞬間のデータのみであり、他のトランザクションのその後の変更はこのトランザクションでは表示されません。

一貫性のある読み取りは、InnoDB が RC および RR 分離レベルで SELECT ステートメントを処理するためのデフォルト モードです。一貫性のある非ロック読み取りでは、アクセスするテーブルにロックが設定されないため、一貫性のある非ロック読み取りがテーブルで実行されている間に、他のトランザクションが同時にテーブルを読み取ったり変更したりできます。

一貫性のある読み取り

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

現在の読書

読み取られるのは最新バージョンです。UPDATE、DELETE、INSERT、SELECT ... LOCK IN SHARE MODE、SELECT ... FOR UPDATE などの操作はすべて現在の読み取りです。なぜ現在の読み取りと呼ばれるのでしょうか?つまり、レコードの最新バージョンを読み取ります。読み取り時には、他の同時トランザクションが現在のレコードを変更できないようにする必要があり、読み取られたレコードはロックされます。

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

読み取られるのはスナップショット バージョン、つまり履歴バージョンです。たとえば、ロック解除された SELECT 操作はスナップショット読み取り、つまりロック解除された非ブロッキング読み取りです。スナップショット読み取りの前提は、分離レベルが非コミット読み取りまたはシリアル読み取りレベルではないことです。これは、非コミット読み取りでは常に最新のデータ行が読み取られ、現在のトランザクション バージョンに準拠するデータ行が読み取られないためです。シリアル読み取りではテーブルがロックされます。

3. 暗黙のロックと明示的なロック

暗黙のロック

InnoDB は、トランザクション実行中に 2 フェーズ ロック プロトコルを使用します (明示的なロックを積極的に実行しません)。

  • ロックはいつでも実行でき、InnoDB は分離レベルに応じて必要に応じて自動的にロックします。
  • ロックはコミットまたはロールバックが実行されたときにのみ解除され、すべてのロックが同時に解除されます。

明示的なロック

  • InnoDBは特定のステートメント(ストレージエンジン層)による明示的なロックもサポートしています。
select ... lock in share mode //共有ロック select ... for update //排他ロック
  • MySQL サーバー層でのロックを表示します。
ロックテーブル
テーブルのロックを解除

上記の概念を理解した後、InnoDBトランザクションがどのように実装されているかを見てみましょう(次の読み取りは、アクティブにロックされていない選択を参照します)。

トランザクション分離レベル実装
コミットされていない読み取り (RU)トランザクションは現在読み取られているデータをロックせず、すべてのデータが現在読み取られます。

トランザクションが特定のデータを更新するとき (つまり、更新が発生した瞬間)、最初に行レベルの共有ロックを追加する必要があり、このロックはトランザクションが終了するまで解放されません。
読み取りコミット (RC)トランザクションは現在読み取られているデータをロックせず、スナップショット読み取りです。

トランザクションが特定のデータを更新するとき (つまり、更新が発生した瞬間)、最初に行レベルの排他ロック (レコード) を追加する必要があり、このロックはトランザクションが終了するまで解放されません。

MySQL はスナップショットを通じて、このレベルでの非反復読み取りの問題を解決します。
繰り返し読み取り (RR)トランザクションは現在読み取られているデータをロックせず、スナップショット読み取りです。

トランザクションが特定のデータを更新するとき (つまり、更新が発生した瞬間)、最初に行レベルの排他ロック (レコード、GAP、Next-Key) を追加する必要があり、このロックはトランザクションが終了するまで解放されません。

ギャップ ロックにより、MySQL はこのレベルでのファントム リードの問題を解決します。
シリアル読み取り (S)トランザクションがデータを読み取るときは、まずテーブル レベルの共有ロックを追加する必要があります。このロックは、トランザクションが終了するまで解放されません。これはすべて現在の読み取りです。

トランザクションがデータを更新する場合、最初にテーブル レベルの排他ロックを追加する必要があります。このロックは、トランザクションが終了するまで解放されません。

ご覧のとおり、InnoDB は MVCC を通じて読み取り書き込み競合の問題を非常にうまく解決し、標準レベルで発生するファントム読み取りと非反復読み取りの問題を 1 レベル先に解決して、データベースの同時実行能力を大幅に向上させます。

よくある誤解

ファントムリードには削除が含まれますか?

反復不可能な読み取り: 行が前後に複数回読み取られ、データの内容に一貫性がなく、他のトランザクションの更新および削除操作です。この問題を解決するには、行共有ロックを使用して、トランザクションの終了までロックします (つまり、RR レベル)。もちろん、MySQL は MVCC を使用して、RC レベルでこの問題を解決します。

ファントム リード: ファントム リードは、同じクエリが異なる時間に異なる行セットを生成する場合に発生します。これは、他のトランザクションの挿入操作によって発生します。この問題を解決するには、トランザクションが終了するまでテーブル全体をロックします (つまり、S レベルです。もちろん、MySQL は RR レベルでギャップ ロックを使用してこの問題を解決します)。

インターネット上の多くの記事では、ファントム リードとコミット リードについて言及しています。ファントム リードには削除も含まれると言う人もいれば、削除はコミット リードの問題として考えるべきだと言う人もいます。では、真実は何でしょうか。実際にMySQLの公式ドキュメントを見てみましょう(以下の通り)

いわゆるファントム問題は、同じクエリが異なる時間に異なる行セットを生成する場合に、トランザクション内で発生します。たとえば、SELECT が 2 回実行され、2 回目に 1 回目に返されなかった行が返された場合、その行は「ファントム」行になります。
次キーロック

ファントム リードは結果セットの前後の変更を対象としていることがわかります。そのため、削除の状況はファントム リードに分類されると思われます。ただし、実際に上記の標準 SQL の実装原理を RR レベルで分析すると、標準 SQL の RR レベルでは、見つかったデータ行に行共有ロックが追加されるため、この時点で他のトランザクションはこれらのデータ行を削除できないことがわかります。したがって、RR では、削除によるファントム リードは発生しません。つまり、ファントム リードには削除は含まれません。

MVCC はファントム リーディング問題を解決できますか?

インターネット上の多くの記事では、MVCC または MVCC+ギャップ ロックがファントム リード問題を解決すると書かれています。実際には、MVCC ではファントム リード問題を解決できません。例えば:

始める;

#users テーブルが空であると仮定すると、以下で見つかったデータは空です。 select * from users; #ロックなし #この時点で、別のトランザクションがコミットされ、id=1 のデータが挿入されます。 select * from users; #スナップショットを読み取ると、見つかったデータは空です。 update users set name='mysql' where id=1; #update は現在の読み取りであるため、更新は成功し、更新されたスナップショットが生成されます。 select * from users; #スナップショットを読み取り、id 1 のレコードを見つけます。MVCC は現在のトランザクションによって生成されたスナップショットを見つけることができるためです。 commit;

前後でチェックしたデータ行に不整合があり、ファントムリードが発生していることがわかります。したがって、MVCC だけではファントム リードの問題を解決することはできません。ファントム リードの問題を解決するには、ギャップ ロックが必要です。次のように:

始める;

#users テーブルが空で、以下が見つかったデータが空であると仮定します。select * from users lock in share mode; #共有ロックを追加します。#この時点で、別のトランザクション B がコミットして、id=1 のデータを挿入します。ギャップ ロックのため、待機する必要があります。select * from users; #スナップショットを読み取ります。見つかったデータは空です。update users set name='mysql' where id=1; #update は現在の読み取りであり、データが存在しないため更新は実行されません。select * from users; #スナップショットを読み取ります。見つかったデータは空です。commit;

#トランザクションBが正常に送信され、データが挿入されます

RR レベルでファントム リード問題を解決するには、明示的にロックする必要があります。そうしないと、クエリ中にロックされません。

上記は、MySQL におけるトランザクション分離レベルの実装原理の詳細な説明です。MySQL トランザクション分離レベルの詳細については、123WORDPRESS.COM の他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • MySQL プロジェクトでトランザクション分離レベルを選択する方法
  • MySql 学習ノートにおけるトランザクション分離レベルの詳細な説明
  • mysql と oracle のデフォルトのトランザクション分離レベルの説明
  • MySql の 4 つのトランザクション分離レベルについて簡単に説明します。
  • トランザクション分離レベルのMySQLケース分析

<<:  Dockerイメージのインポートとエクスポートの実装

>>:  XHTMLコードの一般的なアプリケーション問題をまとめる

推薦する

CSS エラスティック ボックス flex-grow、flex-shrink、flex-basis の詳細な説明

3 つの属性 flex-grow、flex-shrink、flex-basis の機能は次のとおりで...

MySQLでテーブルを作成し、フィールドコメントを追加する方法

コードと例を直接投稿する #テーブル作成時にコメントを記述する CREATE TABLE useri...

ネイティブ js カスタム右クリック メニュー

この記事の例では、参考のためにjsカスタム右クリックメニューの具体的なコードを共有しています。具体的...

Docker+daocloudはフロントエンドプロジェクトの自動構築とデプロイを実現します

自動プロジェクト展開は大企業やユニコーン企業でよく使用され、手動でプロジェクトを展開するよりも効率的...

ドメイン名を指定されたポートに転送するようにNginxを設定する方法

/usr/local/nginx/conf と入力する sudo cd /usr/local/ngi...

JSオブジェクトの走査順序の詳細な説明

JavaScript ではオブジェクトを走査する順序は固定されていないと聞いたことがある人もいるかも...

Vueはページdivボックスのドラッグアンドドロップソート機能を実装します

vue は、ページ上の div ボックスのドラッグ アンド ドロップ ソート機能を実装します。 序文...

Linux ssh サービス情報と実行ステータスを表示する方法

Linux での ssh サービス構成など、ssh サーバー構成に関する記事は多数あります。ここでは...

Vueはmockjsを使用してシミュレートされたデータケースの詳細を生成します

目次プロジェクトにmockjsをインストールするVueプロジェクトでmockjsを使用する基本的なプ...

バッチファイルを処理するLinuxの1行コマンドの詳細な説明

序文最良の方法は、あなたが思いつく最も速い方法ではないかもしれません。職場で一時的に使用するスクリプ...

互換性を維持しながら他のウェブページのデータを適用する iframe の使い方

以下は、Shiji Tiancheng が Tencent KartRider ページを呼び出すため...

MySQL 最適化のヒント: 重複削除の実装方法の分析 [数百万のデータ]

この記事では、MySQL 最適化のヒントで重複したエントリを削除する方法を例を使って説明します。ご参...

JavaScript が Xiaomi のカルーセル効果を模倣

この記事は、透明度を変えてカルーセルにするXiaomiカルーセルを真似て書いたものです。初心者なので...

Vue が天気予報機能を実装

この記事では、天気予報機能を実現するためのVueの具体的なコードを参考までに共有します。具体的な内容...

CentOS 8.0.1905 は ZABBIX 4.4 バージョンをインストールします (検証済み)

Zabbix サーバー環境プラットフォームバージョン: ZABBIX バージョン 4.4システム:...