MySQL の永続性とロールバックの原理を 1 つの記事で理解する

MySQL の永続性とロールバックの原理を 1 つの記事で理解する

再実行ログ

トランザクション サポートは、データベースとファイル システムを区別する重要な機能の 1 つです。トランザクションには、次の 4 つの主要な特性があります。

  • 原子性: すべての操作は実行されるか実行されないかのいずれかであり、分割できません。
  • 一貫性: データベースが 1 つの状態から別の状態に変化した結果は、最終的に一貫性を持ちます。たとえば、A が B に 500 を転送すると、A の金額は 500 少なくなり、B の金額は 500 多くなりますが、A+B の値は変わりません。
  • 分離: トランザクションは互いに分離されており、相互に干渉しません。
  • 永続性: トランザクションがコミットされると、データへの変更は永続的になります。

この記事では主に永続性に関する知識について説明します。

たとえば、トランザクション内のレコードを更新する場合:

ユーザーを更新し、user_id=1 の age=11 を設定します。

そのプロセスはおおよそ次のようになります。

  • まず、user_idデータがあるページがメモリ内にあるかどうかを判断します。メモリ内にない場合は、まずデータベースから読み込んでからメモリにロードします。
  • メモリ内の年齢を11に変更します
  • REDOログを書き込み、REDOログは準備状態です
  • binlogへの書き込み
  • トランザクションをコミットすると、REDOログはコミットされた状態に変わります。

ここで重要なポイントがいくつかあります: REDO ログとは何でしょうか?なぜREDOログが必要なのでしょうか?準備状態のREDOログとは何ですか? redo ログと binlog のどちらか一方だけを選択できるでしょうか? この一連の質問で redo ログを明らかにしましょう。

ディスクデータを直接更新するのではなく、最初にメモリデータを更新する必要があるのはなぜですか?

データを更新するたびに、対応するディスクデータを直接更新しないのはなぜですか?まず、ディスク IO は遅く、メモリは速く、その速度は同じ桁ではないことがわかっています。そのため、遅いディスク IO にはインデックスが登場します。インデックスを使用すると、何億ものデータがあっても、ディスク上で非常に速くデータを見つけることができます。これがインデックスの役割です。ただし、インデックスも維持する必要があり、静的ではありません。新しいデータ A を挿入する場合、このデータは既存のデータ B の後に挿入する必要があるため、A のためのスペースを確保するために B データを移動する必要があり、一定のオーバーヘッドが発生します。さらに悪いことに、データを挿入するページがいっぱいの場合は、新しいページを要求し、一部のデータをそのページに移動する必要があります。これはページ分割と呼ばれ、コストが高くなります。 SQL の変更がディスク上のデータを直接変更することであり、上記の問題が発生した場合、この時点で効率は非常に低くなり、深刻な場合にはタイムアウトが発生します。このため、上記の更新プロセスでは、まず対応するデータ ページをメモリにロードし、次にメモリ内のデータを更新します。 MySQL の場合、すべての変更はまずバッファ プール内のデータを更新する必要があります。その後、バッファ プール内のダーティ ページが一定の頻度でディスクにフラッシュされます (チェックポイントメカニズム)。バッファ プールは、CPU とディスク間のギャップを最適化するために使用され、全体的なパフォーマンスが急激に低下することはありません。

なぜREDOログが必要なのでしょうか?

バッファ プールは CPU とディスク間のギャップを解消するのに役立ち、チェックポイント メカニズムはデータが最終的にディスクに書き込まれることを保証できます。ただし、チェックポイントは変更が発生するたびにトリガーされるのではなく、マスター スレッドによって間隔を置いて処理されます。したがって、最悪のシナリオは、バッファ プールに書き込んだ直後にデータベースがクラッシュし、そのデータが失われて回復できなくなることです。この場合、ACID の D は満たされません。この場合の永続性の問題を解決するために、InnoDB エンジン トランザクションは WAL テクノロジ (Write-Ahead Logging) を採用しています。このテクノロジの考え方は、最初にログを書き込んでからディスクに書き込むというものです。ログが正常に書き込まれた場合にのみ、トランザクションが正常にコミットされたとみなされます。ここでのログは、REDO ログです。クラッシュが発生し、データがディスクにフラッシュされていない場合は、REDO ログを通じてデータを回復し、ACID の D を確保できます。これが REDO ログの役割です。

REDO ログはどのように実装されますか?

再実行ログはディスクに直接書き込まれるわけではありません。再実行ログには、再実行ログ バッファと呼ばれるバッファもあります。InnoDB エンジンは、再実行ログを書き込むときに最初に再実行ログ バッファに書き込み、一定の頻度で実際の再実行ログにフラッシュします。再実行ログ バッファは、通常、それほど大きくする必要はありません。一時的なコンテナにすぎません。マスター スレッドは、1 秒ごとに再実行ログ バッファを再実行ログ ファイルにフラッシュします。したがって、再実行ログ バッファが 1 秒以内にトランザクションによって変更されたデータ量を格納できることを保証するだけで済みます。mysql5.7.23 を例にとると、デフォルトは 16M です。

mysql> '%innodb_log_buffer_size%' のような変数を表示します。
+------------------------+-----------+
| 変数名 | 値 |
+------------------------+-----------+
| innodb_log_buffer_size | 16777216 |
+------------------------+-----------+

ほとんどのアプリケーションでは 16 MB のバッファで十分です。バッファを REDO ログに同期させるには、いくつかの方法があります。

  • マスタースレッドは1秒ごとにバッファをREDOログにフラッシュします。
  • 各トランザクションがコミットされると、バッファは REDO ログにフラッシュされます。
  • バッファの残りスペースが1/2未満になると、REDOログにフラッシュされます。

注意すべき点は、REDO ログ バッファを REDO ログにフラッシュするプロセスは、実際にはディスクにフラッシュするのではなく、OS キャッシュにフラッシュするだけであるということです。これは、ファイル書き込みの効率を向上させるために、最新のオペレーティング システムによって行われる最適化です。実際の書き込みは、システム自体によって決定されます (たとえば、OS キャッシュが十分に大きい場合)。すると、InnoDB に問題が生じます。fsync をシステムに任せていると、システムがクラッシュすると、データも失われます (システム全体がクラッシュする可能性はまだ比較的小さいですが)。このような状況に対応するため、InnoDB はinnodb_flush_log_at_trx_commit戦略を提供し、ユーザーがどちらを使用するかを決定できるようにしています。

mysql> 'innodb_flush_log_at_trx_commit' のような変数を表示します。
+--------------------------------+-------+
| 変数名 | 値 |
+--------------------------------+-------+
| innodb_flush_log_at_trx_commit | 1 |
+--------------------------------+-------+
  • 0: トランザクションがコミットされた後、fsync は実行されませんが、マスターは 1 秒ごとに redo ログの fsync を実行します。
  • 1: デフォルト値、トランザクションがコミットされるたびに fsync が同期的に実行されます
  • 2: OSキャッシュに書き込んだ後、オペレーティングシステムがfsyncするタイミングを決定する

3 つのブラッシュイン戦略から:

2は間違いなく最も効率的ですが、オペレーティングシステムがクラッシュする限り、OSキャッシュ内のデータは失われ、この場合、ACID D

0の場合は妥協です。IO 効率は理論上1より高く、 2より低くなります。データ セキュリティは理論上1より低く、 2より高くなります。この戦略にはデータ損失のリスクもあり、D を保証することはできません。

1 はデフォルト値であり、D とデータが失われないことを保証できますが、効率は最も悪くなります。個人的にはデフォルト値を使用することをお勧めします。オペレーティングシステムがクラッシュする確率は理論的にはデータベースがクラッシュする確率よりも低いですが、トランザクションが使用されるため、データセキュリティが比較的重要になります。

REDO ログはページの物理的な変更です。ページ x の x 番目の位置が xx に変更されます。例:

ページ(2,4)、オフセット64、値2

InnoDB エンジンでは、REDO ログは 512 バイト単位で保存されます。各保存単位は、REDO ログ ブロックと呼ばれます。ページに保存されるログの量が 512 バイトを超える場合は、論理的に複数のブロックに分割して保存する必要があります。

REDO ログ ブロックは、ログ ヘッダー、ログ本体、およびログ テールで構成されます。ログ ヘッダーは 12 バイト、ログ テールは 8 バイトを占めるため、ブロックに実際に保存できるデータは 512-12-8=492 バイトになります。

複数の REDO ログ ブロックが REDO ログを構成します。

各REDOログのデフォルトサイズは48Mです。

mysql> 'innodb_log_file_size' のような変数を表示します。
+----------------------+----------+
| 変数名 | 値 |
+----------------------+----------+
| innodb_log_file_size | 50331648 |
+----------------------+----------+

デフォルトでは、InnoDB は 2 つの REDO ログを使用してログ グループを形成し、このログ グループが実際に作業を実行します。

mysql> 'innodb_log_files_in_group' のような変数を表示します。
+---------------------------+-------+
| 変数名 | 値 |
+---------------------------+-------+
| innodb_log_files_in_group | 2 |
+---------------------------+-------+
#ib_logfile0
#ib_logfile1

ib_logfile0 が終了すると、ib_logfile1 が書き込まれます。ib_logfile1 が終了すると、ib_logfile0 が再び書き込まれ、サイクルが継続します。

ブロックが 512 バイトに設計されているのはなぜですか?

これはディスクのセクターに関係しています。機械式ディスクのデフォルトのセクターは 512 バイトです。書き込むデータが 512 バイトより大きい場合は、複数のセクターに書き込む必要があります。このとき、ディスクは次のセクターを見つけるために回転する必要があります。現在、2 つのセクター A と B に書き込む必要があるとします。セクター A の書き込みが正常に行われ、セクター B の書き込みが失敗すると、非アトミック書き込みが発生します。セクターと同じサイズの 512 バイトのみを書き込む場合、各書き込みはアトミックです。

なぜ 2 段階の提出が必要なのですか?

上記から、トランザクションの送信では、最初に redo ログを書き込み (準備)、次に binlog を書き込み、最後にコミット (コミット) する必要があることがわかります。なぜここに準備アクションがあるのでしょうか? REDO ログをコミットするだけではだめですか? REDO ログが直接送信され、バイナリログの書き込み時にクラッシュが発生した場合、バイナリログには対応するデータが含まれません。すると、バイナリログを使用してデータを回復するすべてのスレーブには対応するデータが含まれなくなり、マスターとスレーブの間に不整合が発生します。したがって、REDO ログと binlog の一貫性を確保するには、2 フェーズ (2pc) 送信を使用する必要があります。具体的な手順は以下のとおりです。準備状態の redo ログには 2PC の XID が記録され、書き込み後の binlog にも 2PC の XID が記録され、redo ログにはコミット マークが付けられます。

REDO ログと bin ログのいずれか 1 つだけが必要になる可能性はありますか?

できません。 REDO ログ自体のサイズは固定されており、いっぱいになると最初からやり直し、古いデータを上書きします。REDO ログはすべてのデータを保存できないため、マスタースレーブモードでは REDO ログを介してスレーブライブラリにデータを同期することは現実的ではありません。その場合、binlog が絶対に必要になります。binlog は MySQL のサーバー層によって生成され、ストレージ エンジンとは関係ありません。binlog はアーカイブ ログとも呼ばれます。binlog ファイルがいっぱいになると、新しい binlog ファイルに書き込まれます。では、binlog だけが必要なのでしょうか? REDOログを必要としないことは可能ですか?もちろん違います。REDO ログの役割は、クラッシュセーフ機能を提供することです。まず、データ変更の場合、バッファプール内のデータページが最初に変更されます。このとき、変更されたデータは実際にはディスクに書き込まれません。これは主に、ディスクの個別の読み取りと書き込みの効率が低いためです。実際にデータをディスクに書き込む作業は、マスタースレッドによって定期的に処理されます。マスターが一度に複数の変更をディスクに書き込むことができるという利点があります。ここで問題が発生します。トランザクションがコミットされた後、データはバッファのダーティ ページにあり、時間内にディスクにフラッシュされていません。このとき、データベースはクラッシュします。その後、データベースを復元してもコミットされたデータを復元できず、ACID の D を満たすことができません。次に、REDO ログがあります。プロセスの観点から、トランザクションの送信は、REDO ログの正常な書き込みを保証する必要があります。REDO ログが正常に書き込まれた場合にのみ、トランザクションが正常に送信されたと見なすことができます。ほとんどの場合、REDO ログはディスクに順番に書き込まれるため、効率がはるかに高くなります。コミット後にクラッシュが発生した場合、REDO ログを通じてデータを回復できるため、REDO ログが必要になります。しかし、トランザクションの送信にはバイナリログの正常な書き込みも必要なので、ディスクに書き込まれていないデータをバイナリログを通じて復元できないのはなぜでしょうか?これは、binlog がディスクに書き込まれたデータを認識できないため、どのデータを復元する必要があるかがわからないためです。 REDO ログの場合、データがディスクに書き込まれた後、対応する REDO ログ内のデータは削除されます。そのため、データベースの再起動後は、REDO ログ内の残りのデータのみを復元する必要があります。

クラッシュ後に回復するにはどうすればいいですか?

2 段階コミットにより、各段階で redo ログと binlog が準備またはコミットでマークされ、トランザクション XID も記録されることがわかります。このデータを使用して、データベースを再起動すると、最初に redo ログ内のすべてのトランザクションがチェックされます。 redo ログ内のトランザクションがコミット状態にある場合、コミット後にクラッシュが発生したことを意味します。このとき、redo ログのデータを復元するだけです。 redo ログが準備状態にある場合、コミット前にクラッシュが発生したことを意味します。このとき、binlog の状態によって現在のトランザクションの状態が決まります。 binlog に対応する XID がある場合、binlog は正常に書き込まれたが、時間内にコミットされなかったことを意味します。このとき、commit を再度実行します。 binlog に対応する XID が見つからない場合、binlog が正常に書き込まれる前にクラッシュしたことを意味するため、この時点でロールバックを実行する必要があります。

元に戻すログ

REDO ログはトランザクションの永続性を保証し、UNDO ログはトランザクションの原子性を保証します。トランザクションでデータを更新する前の操作は、実際には最初にそれを UNDO ログに書き込むことなので、そのプロセスはおおよそ次のようになります。

どのような状況で UNDO ログが生成されますか?

UNDO ログの機能は、MVCC (マルチバージョン コントロール) とロールバックです。ここでは主にロールバックについて説明します。トランザクションで特定のデータを挿入、更新、または削除すると、対応する UNDO ログが生成されます。ロールバックを実行すると、UNDO ログを通じてトランザクションの先頭に戻ることができます。ロールバックは物理ページを変更するのではなく、ロジックを元の状態に戻すことに注意してください。たとえば、トランザクションでデータ A が B に変更されましたが、別のトランザクションですでに C に変更されています。ロールバックによってデータ ページが直接変更され、データが A に変更されると、C は上書きされます。

InnoDB エンジンの場合、各行レコードには、レコード自体のデータに加えて、いくつかの非表示の列があります。

  • DB_ROW_ID : テーブルに明示的に定義された主キーがなく、テーブルに一意のインデックスが定義されていない場合、InnoDB は非表示の列 row_id を主キーとしてテーブルに自動的に追加します。
  • DB_TRX_ID : 各トランザクションにはトランザクション ID が割り当てられます。レコードが変更されると、このトランザクションのトランザクション ID が trx_id に書き込まれます。
  • DB_ROLL_PTR : ロールバック ポインター。基本的には、元に戻すログへのポインターです。

INSERT を実行する場合:

始める;
ユーザー (名前) に値 ("tom") を挿入します

挿入されたデータごとに挿入元に戻すログが生成され、データのロールバック ポインターがそれを指します。 UNDO ログには、UNDO ログのシーケンス番号、主キーに挿入された列と値などが記録されるため、ロールバック時に主キーを通じて対応するデータを直接削除できます。

更新操作の場合、更新取り消しログが生成され、主キーを更新するものと主キーを更新しないものに分割されます。ここで、次のコマンドを実行するとします。

ユーザーを更新します。SET name="Sun" WHERE id=1; 

このとき、古いレコードは新しい UNDO ログに書き込まれ、ロールバック ポインターは UNDO 番号が 1 の新しい UNDO ログを指し、新しい UNDO ログは古い UNDO ログ (UNDO 番号 = 0) を指します。

ここで以下を実行するとします:

ユーザーを更新、SET id=2、WHERE id=1; 

主キーを更新する操作では、まず元のデータの削除マーク フラグがオンになります。この時点では、データは実際には削除されません。実際の削除はクリーンアップ スレッドによって判断され、その後に新しいデータが挿入されます。新しいデータによっても UNDO ログが生成され、UNDO ログのシーケンス番号が増加します。

データが変更されるたびに、UNDO ログが生成されます。レコードが複数回変更されると、複数の UNDO ログが生成されます。UNDO ログには変更前のログが記録され、各 UNDO ログのシーケンス番号は増分されます。そのため、ロールバックする場合は、シーケンス番号に従って前進することで元のデータを見つけることができます。

UNDO ログはどのようにロールバックされますか?

上記の例を例にとると、ロールバックが実行されると仮定すると、対応するプロセスは次のようになります。

  • undo no=3 のログから id=2 のデータを削除します
  • no=2 のログを元に戻すことにより、id=1 のデータの削除マークを 0 に復元します。
  • no=1 のログを取り消して、id=1 のデータの名前を Tom に復元します。
  • no=0 のログを元に戻すことで id=1 のデータを削除します。

元に戻すログはどこにありますか?

InnoDB は、UNDO ログをセグメント、つまりロールバック セグメントで管理します。各ロールバック セグメントには、1024 個の UNDO ログ セグメントが記録されます。InnoDB エンジンは、デフォルトで 128 個のロールバック セグメントをサポートします。

mysql> 'innodb_undo_logs' のような変数を表示します。
+------------------+-------+
| 変数名 | 値 |
+------------------+-------+
| innodb_undo_logs | 128 |
+------------------+-------+

サポートできる同時トランザクションの最大数は 128*1024 です。各 UNDO ログ セグメントは、1024 個の要素を持つ配列を維持するようなものです。

トランザクションを開始して、UNDO ログを書き込む必要がある場合、まず UNDO ログ セグメント内の空き位置を見つける必要があります。空き位置がある場合は、UNDO ページを申請し、最後にこの申請された UNDO ページに UNDO ログを書き込みます。 MySQL のデフォルトのページ サイズは 16k であることがわかっています。

mysql> '%innodb_page_size%' のような変数を表示します。
+------------------+-------+
| 変数名 | 値 |
+------------------+-------+
| innodb_ページサイズ | 16384 |
+------------------+-------+

したがって、1 つのトランザクションに 1 ページを割り当てるのは、実際には非常に無駄が多いことになります (トランザクションが非常に長い場合を除く)。アプリケーションの TPS が 1000 であると仮定すると、1 秒間に 1000 ページが必要になり、約 16 MB のストレージが必要になります。また、1 分間に約 1 GB のストレージが必要になります... この状態が続くと、MySQL を非常に熱心にクリーンアップしない限り、ディスク領域は時間の経過とともに急速に増加し、多くの領域が無駄になります。そのため、UNDO ページは再利用されるように設計されています。トランザクションがコミットされても、UNDO ページはすぐには削除されません。再利用のため、UNDO ページがクリーンでない可能性があり、UNDO ページが他のトランザクションの UNDO ログと混在する可能性があります。 UNDO ログがコミットされた後、リンク リストに配置され、UNDO ページの使用済みスペースが 3/4 未満であるかどうかが判断されます。3/4 未満の場合、現在の UNDO ページが再利用可能であることを意味するため、リサイクルされず、現在の UNDO ページの後ろに他のトランザクションの UNDO ログを記録できます。 UNDO ログは個別であるため、対応するディスク領域をクリーンアップする効率はそれほど高くありません。

これで、MySQL の永続性とロールバックの原理を理解するためのこの記事は終わりです。MySQL の永続性とロールバックに関するより関連性の高いコンテンツについては、123WORDPRESS.COM で以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • MySQL 8の新機能におけるグローバルパラメータの永続性の詳細な説明
  • MySQL 永続統計の詳細な説明
  • MySQL 8 の新機能: 永続的なグローバル変数を変更する方法
  • MySQL 8 の新機能: 自動増分主キーの永続性に関する詳細な説明
  • MySQL マスタースレーブ同期、トランザクションロールバックの実装原理
  • MySQL でトランザクションのコミットとロールバックを実装する方法の詳細な例
  • 誤った操作の後にMySQLデータベースを素早くロールバックする方法
  • MySql トランザクションをロールバックできない理由は何ですか?
  • MySQLはトランザクションのコミットとロールバックの例を実装しています
  • MYSQL トランザクション ロールバックの 2 つの問題の分析

<<:  ウェブデザインでよくある間違いのまとめ

>>:  角度でechartsマップを使用する詳細な説明

推薦する

Windows での MySQL 8.0.12 のインストール手順と基本的な使用方法のチュートリアル

この記事では、WindowsでのMySQL 8.0.12のインストール手順と使用方法のチュートリアル...

ホスト上のDockerコンテナ内でシェルまたはプログラムを実行する

Docker コンテナに繰り返し入って操作することを避けるために、コンテナ内の一連の命令をホストマシ...

nginx ロードバランシングを介して https にリダイレクトする方法

ウェブ上で証明書とキーをコピーするscp -rp -P52113 /application/ngin...

MySQL シャーディングの詳細

1. ビジネスシナリオの紹介MySQLを使用する電子商取引システムがあるとします。大量のデータを保存...

JS 非同期コードユニットテストの魔法 Promise

目次序文プロミスチェーンMDN エラー連鎖デフォルト処理略語非同期待機序文この記事を書いた理由は、ユ...

Dockerがコンテナを起動するたびに、IPとホストが指定した操作が実行されます。

序文Dockerを使ってHadoopクラスタを起動するたびに、ネットワークカードの再バインド、IPの...

モバイルページで縦画面を強制する方法

最近、仕事でモバイルページを作成しました。もともと特別なことではありませんでしたが、非常に奇妙に感じ...

js は丸で囲まれた数字のリストのサンプルコードを動的に追加します

1. まず本文にulタグを追加します <!-- 順序なしリスト --> <ul i...

オーディオマニアにアピールするオーディオビジュアルLinuxディストリビューション

私は最近、多くの音楽に特化した Linux ディストリビューションの 1 つである Audiovis...

Node.js コード実行をバイパスするためのヒントのまとめ

目次1. 子プロセス2. nodejsでのコマンド実行2.1 16進数エンコード2.2 ユニコードエ...

コンテンツ領域の周囲を回転する CSS 動的グラデーション ボーダーの効果 (サンプル コード)

レンダリング ネットで関連情報を調べたところ、現在のダイナミックグラデーションボーダーの実装方法のほ...

WeChatアプレットはキャンバスを使用して時計を描画します

この記事では、キャンバスを使用してWeChatアプレットに時計を描く具体的なコードを参考までに共有し...

JS を使用してクリップボード内の Excel コンテンツを解析する方法

目次序文1. イベントとクリップボードを貼り付ける2. クリップボード内のコンテンツ形式3. HTM...

Nginx rtmp モジュールのコンパイル ARM バージョンの問題

目次1. 準備: 2. ソースコードのコンパイル1. 設定する2. コンパイルエラー3. ターゲット...

MySQLデータベースとOracleデータベース間のバックアップをインポートする

OracleデータベースからエクスポートされたデータをMySqlデータベースにインポートします。 1...