データベースの原則と MySQL アプリケーション | 同時実行制御

はじめに: 同時にデータを変更する必要がある複数のクエリがある場合は常に、同時実行制御の問題が発生します.MySQL は、複数バージョンの同時実行制御とロックによって同時実行制御を実装します.

同時にデータを変更する必要がある複数のクエリがある場合、同時実行制御の問題が発生します.MySQL は、複数バージョンの同時実行制御とロックによって同時実行制御を実装します.

複数のユーザーがトランザクションを同時に実行して同じデータベースにアクセスすると、ダーティ ライト、ダーティ リード、繰り返し不可能なリード、ファントム リードなどの一貫性の問題が発生する可能性があります。同時トランザクションが同じレコードにアクセスする状況は、次の 3 つのタイプに分けられます。

  1. 読んで読んで

つまり、複数の同時トランザクションが同じレコードを次々に読み取ります。読み取り操作によってレコードの内容が変更されないため、この状況が許容されます。

  1. 書き込み-書き込み

つまり、複数の同時トランザクションが同じレコードを次々と変更します。この場合、「ダーティ ライト」現象が発生し、どの分離レベルでもこの​​現象は発生しません。このとき、ロック メカニズムを使用して、これらのコミットされていない同時トランザクションをキューに入れ、順次実行する必要があります。

トランザクションがこのレコードを変更したい場合、最初にそれをロックする必要があり、ロックが成功した場合、トランザクションは操作を続行できます。ロックが失敗した場合、トランザクションは待機する必要があります。

  1. 読み書きまたは書き込み読み取り

つまり、1 つのトランザクションが読み取り操作を実行しており、もう 1 つのトランザクションが書き込み操作を実行しています。この場合、ダーティ リード、繰り返し不可能なリード、ファントム リードが発生する可能性があります。MySQL は、次の 2 つのソリューションを採用しています。

(1) 読み取り操作はマルチバージョン同時実行制御 (MVCC) を使用し、書き込み操作はロックされます。

(2) 読み取り操作と書き込み操作の両方がロックされます。

MVCC 方式を使用すると、読み取り操作と書き込み操作が競合せず、パフォーマンスが向上します。ロック モードでは、読み取り操作と書き込み操作を実行のためにキューに入れる必要があり、パフォーマンスに影響します。

一般に、MVCC は読み取り操作と書き込み操作の同時実行の問題を解決するために使用できますが、一部のビジネス シナリオでは、最新バージョンのレコードを毎回読み取る必要があり、古いバージョンのレコードを読み取ることは許可されていません。この場合、Locking のみ使用できます。

01、MVCC

MVCC (Multi-Version Concurrency Control, Multi-Version Concurrency Control) は、読み取りと書き込みの競合を解決するために使用されるロックフリーの同時実行制御メカニズムです. データベースで同時に実行されるトランザクションを制御するために使用され、トランザクションを分離することができます. その本質は、読み取り操作を実行するときにロックを置き換え、ロックによる負担を軽減することです。書き込み操作ではレコードの最新バージョンを使用し、読み取り操作ではレコードの過去のバージョンを使用するため、異なるトランザクションの読み取り-書き込み操作と書き込み-読み取り操作を同時に実行できるため、データベースの同時実行パフォーマンスが向上します。

MVCC は、特定の時点でのデータのスナップショットを保存することによって制御されます (読み取りビュー)。同じデータ レコードに複数の異なるバージョンを含めることができます。クラスタ化されたインデックス レコードと undo ログの roll_pointer 属性は、レコード バージョン チェーンに連結されます。レコードの特定のバージョンの可視性は、生成されたスナップショットで判断できます。クエリ時に対応する制約を追加することにより、ユーザーが必要とするデータの対応するバージョンが取得されます。

MVCC は、MySQL 分離レベルの READ COMMITTED (コミットされた読み取り) および REPEATABLE READ (反復可能な読み取り) レベルにのみ適用されます。MVCC は、実際には、トランザクションのこれら 2 つの分離レベルを使用して通常の読み取り操作を実行するときに、レコードのバージョン チェーンにアクセスするプロセスです。

1. MVCC と 4 つの分離レベルの関係

1) READ UNCOMMITTED (コミットされていない読み取り)

ダーティ リードが存在するため、つまり、コミットされていないトランザクションのデータ行を読み取ることができるため、MVCC は適用されません。

2) SERIALIZABLE (シリアル化)

InnoDB は関連するテーブルをロックするため、行レベルのロックではなく、行のバージョン管理の問題がないため、MVCC は適用されません。

3) READ COMMITTED (コミット読み取り)

データが読み取られるたびにスナップショットが生成され、古いスナップショットが更新されて、他のトランザクションによってコミットされたコンテンツを読み取れるようになります。

4) REPEATABLE READ (繰り返し可能な読み取り)

スナップショットは、データが初めて読み取られたときにのみ生成され、将来は更新されません。後続のすべての読み取り操作では、このスナップショットが再利用され、各読み取り操作の一貫性が確保されます。

REPEATABLE READ (繰り返し可能な読み取り) の分離レベルは READ COMMITTED (コミットされた読み取り) よりも高くなりますが、スナップショットは頻繁に更新されないため、オーバーヘッドは比較的小さいことがわかります。

2. データ レコードを読み取る 2 つの方法

1) 現在の読み取り

現在のデータの最新バージョンを読み取り、データを読み取った後、データはロックされ、他のトランザクションが変更されないようにします。書き込み操作を実行する場合、データレコードの最新バージョンを読み取るために「現在の読み取り」が必要です。

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

実際には、古いバージョンの情報を含む、データのすべてのバージョン情報を読み取ることができる MVCC でスナップショットを読み取ることです。つまり、「スナップショット読み取り」が読み取ったものは、必ずしも最新バージョンのデータではなく、以前の履歴バージョンである可能性があります。

READ COMMITTED 分離レベルでは、「スナップショット読み取り」と「現在の読み取り」は同じ結果になり、どちらも送信されたデータの最新バージョンを読み取ります。

REPEATABLE READ (反復可能読み取り) 分離レベルでは、「現在の読み取り」は他のトランザクションによって送信された最新のバージョンのデータであり、「スナップショットの読み取り」は現在のトランザクションの前に読み取られたバージョンです。読んだバージョン。

MySQL では、MVCC は、レコード、取り消しログ、スナップショットなどの 3 つの暗黙的な列によって実装されます。

02. ロック機構

MySQL はさまざまなストレージ エンジンをサポートしており、ストレージ エンジンごとにロック メカニズムも異なります。例: MyISAM および MEMORY ストレージ エンジンはテーブル レベルのロックのみをサポートし、 InnoDB ストレージ エンジンは行レベルのロックとテーブル レベルのロックの両方をサポートしますが、デフォルトでは行レベルのロックが使用されます。

1. ロックの分類

1) ロック粒度による分類

理論的には、現在操作中のデータのみをロックするたびに最大の同時実行性が得られますが、ロックの管理は非常にリソースを消費します。したがって、データベース システムは、高い同時応答とシステム パフォーマンスのバランスを取る必要があり、「ロックの粒度」という概念が生まれます。

ロックの粒度、つまりロックされたデータ範囲は、ロック管理のオーバーヘッドと同時実行パフォーマンスの関係を測定できます。ロックの粒度が大きいほど、ロック範囲が大きくなり、ロックを管理するオーバーヘッドが低くなり、同時実行性が低下します。大から小までのロックの粒度に応じて、次の 3 種類のロックに分けることができます。

(1) テーブル レベル ロック (テーブル レベル ロック): テーブル ロックとも呼ばれ、テーブルをロックするために使用されます。ロックの種類によっては、他のユーザーはテーブルにレコードを挿入できず、テーブルからのデータの読み取りも制限されます。テーブル レベルのロックには、読み取りロックと書き込みロックの 2 種類があります。

テーブル レベルのロックは、オーバーヘッドが低く、ロックが高速であること、デッドロックがないこと、ロックの粒度が大きいこと、ロック競合の可能性が最も高いこと、同時実行性が最も低いことを特徴としています。

(2) ページ レベル ロック: データ レコードが配置されているページをロックするために使用され、他のデータベース管理ソフトウェアでは一般的ではない、MySQL 固有のロック レベルです。ページは、ディスクとメモリ間の相互作用の基本単位であり、ページ サイズは通常 16KB です。

ページ レベル ロックの特徴は、オーバーヘッドとロック時間がテーブル ロックと行ロックの間にあること、デッドロックが発生する可能性があること、ロックの粒度がテーブル ロックと行ロックの間であること、同時実行性が平均的であることです。

(3) 行レベルのロック (行レベルのロック): 行ロックとも呼ばれ、行 (つまり、レコード) をロックするために使用されます。この場合、スレッドが使用する行のみがロックされ、テーブル内の他の行は他のスレッドで使用できるため、行レベルのロックは同時処理を最大限にサポートできます。行レベル ロックは、MySQL によって提供されるロック メカニズムではなく、ストレージ エンジン自体によって実装されます. InnoDB のロック メカニズムは行レベル ロックです。

行レベルのロックは、オーバーヘッドが高く、ロックが遅いという特徴があります。デッドロックが発生する可能性があります。ロックの粒度は最小であり、ロック競合の確率は最小であり、同時実行性は最大です。行レベルのロックには、排他ロック、共有ロック、インテント ロックの 3 種類があります。

テーブル レベルのロックはデータベース サーバーによって実装され、行レベルのロックはストレージ エンジンによって実装されます。データ ロックの範囲が小さいほど、データベースの同時実行性が向上します。

2) データベース操作の種類による分類

トランザクションは、データベースに対して読み取りまたは書き込みを行うことができます. データベース操作の種類に応じて、次の 4 つの種類のロックに分けることができます。

(1) 共有ロック (Shared Lock): 読み取りロックとも呼ばれ、S ロックと呼ばれます。トランザクションがレコードを読み取りたい場合、最初にレコードの共有ロックを取得する必要があります。読み取り操作では、レコード データは変更されません。複数の読み取り操作は、互いに影響を与えることなく同時に実行できます。複数のトランザクションが、同じレコードに同時に共有ロックを追加できます。共有ロックでは、排他ロックを取得できません。

(2) 排他的ロック: 排他的ロック、書き込みロック、または略して X ロックとも呼ばれます。トランザクションがレコードを変更する場合、最初にレコードの排他ロックを取得する必要があります。現在のトランザクションの書き込み操作が完了するまで、他の排他的および共有ロックをブロックします。現在のトランザクションがコミットされると、排他ロックが解放されます。

(3) 意図共有ロック: 略して IS ロック。トランザクションがレコードに共有ロックを追加しようとしている場合、最初にテーブル レベルでインテント共有ロックを追加する必要があります。

(4) 意図排他ロック:略してIXロック。トランザクションがレコードに排他ロックを追加しようとしている場合、最初にテーブル レベルで意図的な排他ロックを追加する必要があります。

ヒント

意図的な共有ロックと意図的な排他ロックは、テーブル レベルのロックです.MySQL は、後でテーブル レベルの共有ロックと排他ロックを追加するときに、テーブル内のレコードがロックされているかどうかをすばやく判断するように設計されているため、トラバーサルによる表示を回避できます。テーブル内のレコード。表 12-1 は、テーブル レベルでのこれら 4 つのロックの互換性を示しています。

■ 表 12-1 テーブルレベルロックの互換関係

2. InnoDB ストレージ エンジンでロックを管理する

1) テーブルレベルでの共有ロックと排他ロック

(1) テーブルレベルのロックを設定するための基本的な構文は次のとおりです。

以下に構文を説明します。

tbl_name [[AS] エイリアス] は「テーブル名 [エイリアスとして]」です。

READ は、テーブルに共有ロックを追加することです。

WRITE は、テーブルに排他ロックを追加することです。

(2) データテーブルのロック操作が完了したら、ロックを解除する必要があります. 基本的な構文形式は次のとおりです.

ヒント

InnoDB ストレージ エンジンのテーブル レベルの共有ロックと排他ロックは、いくつかの特殊なケース (システム クラッシュ リカバリなど) でのみ使用されます.テーブルで SELECT、INSERT、UPDATE、DELETE およびその他のステートメントを実行する場合、InnoDB ストレージ エンジンは Noテーブル レベルの共有ロックまたは排他ロックがこのテーブルに追加されます。もちろん、ユーザーは、必要に応じて LOCK TABLES などの手動ロック テーブル ステートメントを使用して、InnoDB ストレージ エンジンを使用してテーブルにテーブル レベルのロックを手動で追加できますが、これは追加の保護を提供しないだけでなく、可能な限り回避する必要があります。ただし、同時実行機能は低下します。

また、あるテーブルに対して ALTER TABLE、DROP TABLE などの DDL ステートメントを実行する場合、このテーブルに対して SELECT、INSERT、DELETE、UPDATE などの DML ステートメントを同時に実行すると、他のトランザクションがブロックされます。同様に、トランザクションがテーブルで DML ステートメントを実行すると、このテーブルで DDL ステートメントを実行する他のトランザクションもブロックされます。

このプロセスは、サーバー層で MetaData Lock (MDL) を使用して実装されます. 通常、ストレージ エンジンによって提供されるテーブル レベルの共有ロックと排他ロックは使用されません。

スペースの都合上、メタデータ ロックについてはここでは紹介しません。各自で他の資料を参照してください。

[例 12-11] 銀行データベースの account テーブル accounts を共有ロックの形式でロックします。

(1) アカウント テーブル accounts に共有ロックを追加します。

(2) accounts テーブルの accounts のデータをクエリします。

アカウント テーブル accounts 内のすべてのレコードをクエリできます。これは、テーブルに共有ロックを追加した後、テーブルに対して通常の読み取り操作を実行できることを示しています。

(3) テーブル内のレコードを削除します。

この時点で、レコードの削除中にエラーが発生し、次のエラー メッセージが表示されます。テーブルを削除できません。

(4) 既存のセッション ウィンドウを開いたままにして、新しいセッション ウィンドウで accounts テーブル accounts のデータをクエリします。アカウント テーブル accounts のすべてのレコードは、新しいセッション ウィンドウでクエリできます。これは、共有ロックが他のセッションと互換性があることを示しています。

(5) この新しいセッション ウィンドウに挿入ステートメントを追加します。

実行結果を見ると、ステートメントが「処理中」の待ち状態で、結果が表示されていないことがわかります。アカウント テーブル accounts には共有ロックがあるため、他のユーザーはそれに書き込むことができません。

(6) 前のセッション ウィンドウでロック解除ステートメントを入力します。

実行結果からわかるように、アカウント テーブル accounts の共有ロックが解除された後、insert ステートメントはすぐに正常に実行され、新しく追加されたレコードはクエリ ステートメントでクエリできますが、insert ステートメントの実行時間はステートメントの待機時間と実行時間の合計です。

2) 行レベルの共有ロックと排他ロック

ユーザーが InnoDB ストレージ エンジンのテーブルに対して INSERT、UPDATE、DELETE などの書き込み操作を実行する前に、ストレージ エンジンは関連するレコードに行レベルの排他ロックを自動的に追加します。ステートメントが実行されると、ストレージ エンジンによって自動的にロックが解除されます。

ただし、通常の SELECT ステートメントの場合、 InnoDB ストレージ エンジンは自動的にロックしません。現在のトランザクションでクエリされたデータが他のトランザクションによって更新または削除されないようにし、ダーティ リード、反復不可能なリード、ファントム リードなどの一貫性の問題を回避するには、行レベルの共有ロックを明示的に追加する必要があります。およびクエリ操作への排他的ロック ロック。

(1) クエリステートメントで行レベルの共有ロックを設定します。基本的な構文形式は次のとおりです。

以下に構文を説明します。

FOR SHARE は、クエリ時に行レベルの共有ロックを追加することを意味し、その後に NOWAIT | SKIP LOCKED が続きます。これら 2 つのパラメーターは MySQL 8.0 の新機能です。

NOWAIT はオプションのオプションで、FOR SHARE または FOR UPDATE クエリをすぐに実行し、別のトランザクションがロックを保持しているために行ロックを取得できない場合にエラーを返します。

SKIP LOCKED はオプションのオプションです。これは、FOR SHARE または FOR UPDATE クエリがすぐに実行され、別のトランザクションによってロックされた行が結果セットに含まれないことを意味します。

LOCK IN SHARE MODE は、クエリ時に行レベルの共有ロックを追加することも意味します。これは、FOR SHARE と同じ機能です。

(2) クエリステートメントで行レベルの排他ロックを設定します。基本的な構文形式は次のとおりです。

文法の説明: FOR UPDATE はクエリ中に行レベルの排他ロックを追加することを意味し、その後に NOWAIT | SKIP LOCKED パラメータが続きます。意味は上記と同じです。

ヒント

上記の行レベル ロックのライフ サイクルは非常に短く、行レベル ロックのライフ サイクルは、トランザクションを手動で開くことによって延長できます. トランザクション内の行レベル ロックのライフ サイクルは、ロックから始まります.トランザクションがコミットまたはロールバックされるまで終了しません。

[例 12-12] bank データベースの account テーブル accounts に行レベル ロックを追加します。

(1) 2 つのセッション ウィンドウを開き、銀行データベースに切り替えます。

(2) セッション ウィンドウ 1 で、accounts テーブルの id 値が 1 である行に排他ロックを追加します。

(3) セッションウィンドウ 2 で、次のコードを入力します。

上記のコードを実行すると、アカウント A の情報を正常に照会できます。

(4) セッションウィンドウ 2 で、引き続き次の SQL ステートメントを入力します。

上記コード実行後、処理を行っており、結果が表示されず、排他ロック待ち状態となります。一定時間待つと、「[Err] 1205 -Lock wait timeout exceeded; try restarting transaction.」というロック待ちタイムアウトプロンプトが表示されます。

セッション ウィンドウ 2 が待機中にセッション ウィンドウ 1 で "ROLLBACK;" コマンドを入力すると、セッション ウィンドウ 2 がすぐに正常に実行されます。

(5) セッションウィンドウ 2 に「ROLLBACK;」文または「COMMIT;」文を入力し、トランザクションを終了します。

ヒント

Row-level locks are only applied to the InnoDB storage engine. テーブルが InnoDB ストレージ エンジンでない場合は、"ALTER TABLE tablename ENGINE = storage engine name;" ステートメントを使用して変更できます。もちろん、大量のデータが存在する実際の運用環境では、ストレージ エンジンを安易に変更しないことが最善です。

次のステートメントを使用して、テーブルのストレージ エンジンを表示できます。実行結果を図 12-2 に示します。

■ 図 12-2 テーブルのストレージ エンジンの表示

3. InnoDB で一般的に使用される行レベルのロック タイプ

InnoDB ストレージ エンジンで一般的に使用される行レベル ロックには、次の 3 つのタイプがあります。

(1) レコードロック: レコード自体のみをロックするレコードロックで、インデックス行をロックすることで実現されます。テーブルにインデックスが定義されていない場合でも、レコード ロックはインデックス レコードをロックします。テーブルがインデックスなしで作成された場合、 InnoDB ストレージ エンジンはロックに暗黙的な主キーを使用します。

(2) ギャップ ロック: レコード間のギャップをロックするために使用されるギャップ ロック。他のトランザクションが新しいレコードをギャップに挿入するのを防ぎます。REPEATABLE READ (反復可能読み取り) トランザクション分離レベルに設定されます。程度発生します。

(3) Next-key Lock: Record Lock と Gap Lock の組み合わせで、レコード自体を保護するだけでなく、他のトランザクションが新しいレコードをギャップに挿入するのを防ぎます。InnoDB ストレージ エンジンは、行クエリにこのロック方法を使用します。

4. トランザクション ロックを表示する

InnoDB ストレージ エンジンでは、次の方法を使用して、トランザクションのロック ステータスを確認できます。

(1) MySQL コンソールで次のステートメントを使用します。

このコマンドによって出力される情報量は非常に多く、出力の複数のセクションに分割されています。各セクションは InnoDB ストレージ エンジンのさまざまな部分に対応しているため、ユーザーは InnoDB ストレージ エンジンの実行ステータスを理解できます。開発者と保守担当者、特にデッドロック分析とパフォーマンス チューニングを行う場合。

TRANSACTIONS 部分はトランザクションに関する統計情報ですが、このステートメントだけでは、どのトランザクションがどのロックをどのレコードに追加したかを示すことはできません。You can set the system variable innodb_status_output_locks first to ON, and then run this command. SQL ステートメントは次のように実行されます。

例12-12のトランザクションを例に取ると、TRANSACTIONSパートの出力は次のようになります。

このように、どのトランザクションがどのロックをどのレコードに追加するのかが明確に表示されます。出力分析の一部は次のとおりです。

① RECORD LOCKS space id 27 page no 4 n bits 72 index PRIMARY of table 'bank1'.'accounts' trx id 3859 lock_mode X は rec をロックしますが、ギャップ待機はロックしません

ここでの出力はロック構造を表し、スペース ID は 27、ページ番号は 4、n_bits 属性値は 72、対応するインデックスはクラスター化インデックス PRIMARY、格納されたロック タイプは X タイプ レコード ロック (排他的) です。ロック)。このステートメントの後の出力は、ロックされたレコードの詳細情報です。

② TABLE LOCK table 'bank1'.'accounts' trx id 3859 lock modeIX

ここでの出力は、ID 3859 のトランザクションが、銀行データベースの口座テーブルにテーブル レベルのインテント排他ロックを追加したことを示しています。

(2) 現在実行中のトランザクション情報は、システム データベース information_schema の INNODB_TRX テーブルで確認できます。出力の一部を図 12-3 に示します。

■ 図 12-3 INNODB_TRX テーブルの出力の一部

上記の出力から、トランザクション ID、ステータス、開始時刻、分離レベル、およびその他の情報を確認できます。その中で、trx_tables_locked は、トランザクションに追加されたテーブル レベルのロックの数を示し、trx_rows_locked は、追加された行レベルのロックの数を示し、trx_lock_structs は、トランザクションによって生成されたメモリ内ロック構造の数を示します。

5.デッドロック

デッドロック (Dead Lock) とは、2 つ以上のプロセスが同じデータを使用する必要があることを意味します. 実行プロセス中は、常に相手がリソースを解放するのを待っている状態になります. 外力がなければ、常にデッドロックが発生しました。

MySQL がデッドロックを検出すると、より小さいトランザクションを選択してロールバックし、次のエラー メッセージを表示します: [Err] 1213 -ロックを取得しようとしているときにデッドロックが見つかりました; トランザクションを再起動してください。

ヒント: いわゆる小規模なトランザクションとは、トランザクションの実行中に影響を受けるレコードが少ないトランザクションを指します。

MySQL 8.0 では、ロックを取得できない場合、NOWAIT、SKIP LOCKED パラメータを追加すると、ロック待機をスキップするか、ロックをスキップします。

「SHOW ENGINE INNODB STATUS \G」ステートメントを使用して、最新のデッドロック情報を表示できます。デッドロックが頻繁に発生する場合は、グローバル システム変数 innodb_print_all_deadlocks を ON に設定し、デッドロックが発生するたびにその情報を MySQL エラー ログに記録すると、エラー ログを表示してより多くのデッドロックを分析できます。

おすすめ

転載: blog.csdn.net/m0_69804655/article/details/130129082