技術共有

MySQL実践45講座学習ノート(随時更新中…)

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina


1. インフラストラクチャ: SQL クエリ ステートメントはどのように実行されますか?

概要

ここに画像の説明を挿入します

一般に、MySQL は 2 つの層に分けることができます。

  • サーバー層
    MySQL のコアサービス機能のほとんどをカバー
    • コネクタ
    • クエリキャッシュ
    • アナライザ
    • オプティマイザ
    • アクチュエーター
    • すべての組み込み関数 (日付、時刻、数学関数、暗号関数など)
    • ストレージ エンジン全体の機能
      • ストアドプロシージャ
      • 引き金
      • ビュー
      • ……
  • ストレージエンジン層
    データの保存と取得を担当するプラグイン アーキテクチャ
    • イノDB
    • マイISAM
    • メモリ

コネクタ

mysql -h$ip -P$port -u$user -p
  • 1

接続コマンドの mysql は、サーバーとの接続を確立するために使用されるクライアント ツールです。従来の TCP ハンドシェイクが完了すると、コネクタは
この時点で、入力したユーザー名とパスワードが使用されます。

  • ユーザー名またはパスワードが間違っている場合は、「ユーザーのアクセスが拒否されました」というエラーが表示され、クライアント プログラムが
    実行を終了します。
  • ユーザー名とパスワードの認証に合格すると、コネクタは権限テーブルそこでどのような権限を持っているかを確認してください。その後、これに関連して、
    パーミッションの判定ロジックは、この時に読み込んだパーミッションに依存します。

ここに画像の説明を挿入します
クライアントが非アクティブな状態が長時間続くと、コネクタは自動的に切断します。この時間はパラメータ wait_timeout によって制御され、デフォルト値は 8 時間です。

接続が切断された後にクライアントが再度リクエストを送信すると、エラー リマインダーが表示されます。 Lost connection to MySQL server during query 。この時点で続行したい場合は、再接続してからリクエストを実行する必要があります。

データベースでは、接続が長いとは、接続が成功した後、クライアントがリクエストを継続する場合、常に同じ接続が使用されることを意味します。短い接続とは、いくつかのクエリが実行された後に接続が切断され、次のクエリのために新しい接続が再確立されることを意味します。

接続を確立するプロセスは通常複雑なので、使用中に接続を確立するアクションを最小限に抑える、つまり長い接続を使用するようにすることをお勧めします。

ただし、長い接続をすべて使用すると、MySQL によって占有されるメモリが急速に増加することがあります。これは、これが原因です。MySQL の実行中に一時的に使用されるメモリは接続オブジェクトで管理されます。 。これらのリソースは、接続が切断されると解放されます。それで、もし長時間の接続が蓄積されると、過剰なメモリ使用量が発生する可能性があります。、現象から判断すると、MySQLが異常再起動したと思われます。

この問題を解決するにはどうすればよいでしょうか?次の 2 つのオプションを検討できます。

  • 長い接続を定期的に切断する 。一定期間使用した後、またはメモリを消費する大規模なクエリが実行されたとプログラムが判断した後、接続は切断され、クエリが必要になってから再接続されます。
  • MySQL 5.7 以降を使用している場合は、次のように実行できます。 mysql_reset_connection 接続リソースを再初期化します。このプロセスでは再接続や権限の確認は必要ありませんが、接続は作成されたばかりの状態に復元されます。

クエリキャッシュ

MySQL はクエリ リクエストを取得すると、まずクエリ キャッシュに移動して、このステートメントが以前に実行されたかどうかを確認します。以前に実行されたステートメントとその結果は、キーと値のペアの形式でメモリに直接キャッシュされる場合があります。 キーはクエリ ステートメントであり、値はクエリ結果です。クエリがこのキャッシュ内で直接キーを見つけることができた場合、値はクライアントに直接返されます。

ステートメントがクエリ キャッシュにない場合は、実行フェーズが続行されます。実行が完了すると、実行結果はクエリ キャッシュに保存されます。クエリがキャッシュにヒットすると、MySQL は後続の複雑な操作を実行せずに結果を直接返すことができ、非常に効率的であることがわかります。

しかし、ほとんどの場合、私はそうしますクエリ キャッシュを使用しないことをお勧めします。 、なぜ?クエリ キャッシュは多くの場合、良いことよりも害を及ぼすためです。

クエリ キャッシュは非常に頻繁に無効になり、テーブルが更新されている限り、このテーブル上のすべてのクエリ キャッシュがクリアされます。そのため、せっかく結果を保存したのに、使用する前にアップデートによって消えてしまったという可能性もあります。更新圧力が高いデータベースの場合、クエリ キャッシュのヒット率は非常に低くなります。あなたのビジネスに、長い間 1 回しか更新されない静的なテーブルがある場合を除きます。たとえば、システム構成テーブルの場合、このテーブルのクエリはクエリ キャッシュに適しています。

幸いなことに、MySQL はこの「オンデマンドで使用する」方法も提供します。 query_cache_type パラメータを DEMAND に設定すると、デフォルトの SQL ステートメントにクエリ キャッシュが使用されなくなります。クエリ キャッシュを使用することが確実なステートメントの場合は、次のステートメントのように SQL_CACHE を使用して明示的に指定できます。

select SQL_CACHE * from T where ID=10;
  • 1

認識しなければならないのは、MySQL 8.0 バージョンはクエリ キャッシュ機能全体を直接削除しますつまり、この機能は 8.0 以降では利用できなくなります。

アナライザ

クエリ キャッシュがヒットしない場合は、ステートメントの実際の実行が開始されます。まず、MySQL はユーザーが何をしたいのかを知る必要があるため、SQL ステートメントを解析する必要があります。

ここに画像の説明を挿入します

オプティマイザ

ここに画像の説明を挿入します
ここに画像の説明を挿入します

アクチュエーター

ここに画像の説明を挿入します
ここに画像の説明を挿入します

2. ロギング システム: SQL 更新ステートメントはどのように実行されますか?

ここに画像の説明を挿入します

再実行ログ

「Kong Yiji」という記事をまだ覚えているかどうかはわかりませんが、ホテルの支配人は宿泊客の信用記録を記録するために特別に使用されるピンクのボードを持っています。クレジットで支払う人が少ない場合は、顧客の名前と口座をボードに書くことができます。しかし、クレジットアカウントを持っている人が多すぎると、ファンボードがそれらを追跡できない場合が常にあります。このとき、店主はクレジットアカウントを記録するための専用の台帳を持っている必要があります。

誰かが信用を返済したい、または借金を返済したい場合、店主には通常 2 つの選択肢があります。

  • 1 つの方法は、台帳を直接開き、信用口座を追加または減算することです。
  • 別のアプローチは、まずは今回の会計をピンクのボードに書き込み、閉店後に帳簿を取り出して計算します。

商売が繁盛してカウンターが混雑しているとき、店主は間違いなく選択します後者 , 前者の操作が面倒すぎるためです。まず、この人の信用口座の合計記録を見つけなければなりません。考えてみると、何十ページものページがぎっしりと詰まっており、名前を見つけるには、店主が老眼鏡をかけてゆっくり探し、そろばんを取り出して計算し、最後にその結果をメモに書き戻す必要があるかもしれません。台帳。

このプロセス全体を考えるのは面倒です。逆に、最初にピンクのボードに書き出すと簡単です。考えてみてください。ピンクのボードの助けがなければ、店主は会計を記録するたびに帳簿をひっくり返さなければなりません。効率が耐えられないほど低いではありませんか。

同様に、この問題は MySQL にも存在します。すべての更新操作をディスクに書き込む必要があり、更新前にディスクで対応するレコードを見つける必要がある場合、プロセス全体の IO コストと検索コストが非常に高くなります。この問題を解決するために、MySQL の設計者は、ホテルの店主のピンクのボードに似たアイデアを使用して更新効率を向上させました。

ピンクのボードと台帳の間の連携プロセス全体は、実際には MySQL でよく言及されるものです。 WAL テクノロジー、WAL フルネームはWrite-Ahead Logging、重要なポイントは最初にログを書き込み、次にディスクに書き込みます、つまり、最初にピンクのボードを書き、忙しくないときに家計簿を書きます。

具体的には、レコードを更新する必要がある場合、InnoDB エンジンはまずレコードを REDO ログ (ピンクのボード) に書き込み、メモリを更新します。この時点で更新は完了します。同時に、InnoDB エンジンは適切なタイミングで操作記録をディスクに更新します。この更新は、店主が閉店後に行うのと同じように、システムが比較的アイドル状態のときに行われることがよくあります。

今日のクレジット口座がそれほど多くない場合、店主は閉店時間まで商品を整理するのを待つことができます。しかし、ある日にたくさんのクレジット口座があり、ピンクのボードがいっぱいになった場合はどうすればよいでしょうか?このとき、店主は作業を中止し、ピンクのボード上の信用記録の一部を台帳に更新し、新しい口座を作成するためのスペースを確保するためにピンクのボードからこれらの記録を消去する必要がありました。

同様に、InnoDB の REDO ログは固定サイズです。たとえば、各ファイルのサイズが 1 GB の 4 つのファイルのセットとして構成でき、この「ピンクのボード」には合計 4 GB の操作を記録できます。以下の図のように、最初から書き始めて、最初に戻ってループして書きます。

ここに画像の説明を挿入します
write pos は現在のレコードの位置で、ファイル No.3 の最後まで書き込んだ後、ファイル No.0 の先頭に戻ります。 チェックポイントは消去される現在位置であり、レコードを消去する前にレコードをデータ ファイルに更新する必要があります。

書き込み pos とチェックポイントの間のスペースは、新しい操作を記録するために使用できる「ピンクのボード」の空の部分です。書き込み pos がチェックポイントに追いついた場合は、「ピンクのボード」がいっぱいで、現時点では新しい更新を実行できないことを意味します。チェックポイントを進めるには、まずいくつかのレコードを停止して消去する必要があります。

REDO ログを使用すると、InnoDB はデータベースが異常に再起動した場合でも、以前に送信されたレコードが失われないことを保証できます。この機能は、と呼ばれます。crash-safe

クラッシュセーフの概念を理解するために、以前の信用記録の例を考えてみましょう。信用記録がピンクのボードに記録されているか台帳に記載されていれば、店主が数日間突然営業を停止するなど、後で忘れてしまったとしても、台帳のデータから信用口座を明らかにすることができ、営業再開後のピンクボード。

バイナリログ

前に述べたように、MySQL 全体には実際には 2 つの部分があります。1 つは主に MySQL の機能レベルで処理を行うサーバー層で、もう 1 つはストレージに関連する特定の事項を担当するエンジン層です。上で話したピンクのボード REDO ログは InnoDB エンジン固有のログです、そして サーバー層には、binlog (アーカイブ ログ) と呼ばれる独自のログもあります。

なぜログが 2 つあるのかと疑問に思われると思います。

それは、最初の MySQL には InnoDB エンジンがなかったからです。 MySQL 独自のエンジンは MyISAM ですが、MyISAM にはクラッシュセーフ機能がなく、binlog ログはアーカイブにのみ使用できます。 InnoDB は、別の会社によってプラグインの形で MySQL に導入されました。binlog のみに依存するとクラッシュ セーフ機能がないため、InnoDB は別のログ システム、つまり REDO ログを使用してクラッシュ セーフ機能を実現します。

これら 2 つのログには、次の 3 つの違いがあります。

  1. REDO ログは InnoDB エンジンに固有であり、binlog は MySQL のサーバー層によって実装され、すべてのエンジンで使用できます。
  2. REDO ログは物理ログです、「特定のデータページにどのような変更が加えられたか」を記録します。binlog は論理ログです, 記録されるのは、「ID=2の行のcフィールドに1を加算する」といった、このステートメントの本来のロジックです。
  3. REDO ログはループで書き込まれます、スペースが使い果たされます。binlogを追加で書き込むことができます 。 「追加書き込み」とは、binlog ファイルが特定のサイズに達した後、次のファイルに切り替わり、前のログを上書きしないことを意味します。

これら 2 つのログの概念を理解した上で、この単純な更新ステートメントを実行するときのエグゼキューターと InnoDB エンジンの内部プロセスを見てみましょう。

  1. エグゼキュータはまずエンジンを探して行 ID=2 を取得します。 ID が主キーであり、エンジンはツリー検索を直接使用してこの行を見つけます。 ID=2 の行が配置されているデータ ページがすでにメモリ内にある場合は、そのデータ ページがエグゼキュータに直接返されます。そうでない場合は、最初にディスクからメモリに読み込まれてから返される必要があります。
  2. エグゼキュータはエンジンによって与えられた行データを取得し、この値に 1 を加えます。たとえば、以前は N でしたが、現在は N+1 になり、新しいデータ行を取得して、エンジン インターフェイスを呼び出してこれを書き込みます。新しいデータ行。
  3. この時点で、エンジンはこの新しいデータ行をメモリに更新し、更新操作を REDO ログに記録します。 再実行ログ準備する州。次に、実行が完了し、いつでもトランザクションを送信できることを実行者に通知します。
  4. エグゼキュータはこの操作のバイナリログを生成し、 ディスクに書き込まれたビンログ
  5. エグゼキューターはエンジンのコミット トランザクション インターフェイスを呼び出し、エンジンは 再実行ログ 送信に変更 (専念)の状態であればアップデートは完了です。

この update ステートメントの実行フローチャートを示します。図の明るいボックスは InnoDB 内で実行されることを示し、暗いボックスはエグゼキューターで実行されることを示します。

ここに画像の説明を挿入します
update文実行処理

最後の 3 つのステップが少し「循環的」であることに気づいたかもしれません。REDO ログの書き込みは準備とコミットの 2 つのステップに分かれています。これは「2 フェーズ コミット」です。

2フェーズコミット

なぜ「2段階提出」が必要なのでしょうか?これは、2 つのログ間の差異を許容するためです。論理的に一貫した 。この問題を説明するには、この記事の冒頭の質問から始める必要があります。「データベースを半月以内に任意の秒の状態に復元するにはどうすればよいですか?」

前にも述べたように、binlog はすべての論理操作を記録し、「追記書き込み」の形式を採用します。 DBA が半月以内に復元できると約束した場合、バックアップ システムは確実に過去半月のすべてのバイナリ ログを保存し、データベース全体の定期的なバックアップを実行します。ここでの「定期的」とは、システムの重要性に応じて、1 日に 1 回または週に 1 回にすることができます。

指定した秒に復元する必要がある場合 (たとえば、ある日の午後 2 時)、テーブルが正午に誤って削除されたことに気づき、データを取得する必要がある場合は、次のように実行できます。

  • まず、最新の完全バックアップを見つけます。運が良ければ、それが昨夜のバックアップである可能性があり、このバックアップから一時データベースに復元します。
  • 次に、バックアップ時点から開始して、バックアップ バイナリログが順番に取得され、正午にテーブルが誤って削除される前の時点まで再生されます。
    このようにすると、一時データベースは誤って削除する前のオンライン データベースと同じになり、必要に応じて一時データベースからテーブル データを取り出してオンライン データベースに復元できます。

さて、データ回復プロセスについて話した後、戻ってログに「2 フェーズ コミット」が必要な理由について話しましょう。ここでは、矛盾による証明を使って説明することもできます。

REDO ログと binlog は 2 つの独立したロジックであるため、2 フェーズ コミットを使用しない場合は、REDO ログを最初に書き込んでから binlog を書き込むか、その逆の順序を採用する必要があります。これら 2 つの方法にはどのような問題があるかを見てみましょう。

引き続き、前の update ステートメントを例として使用します。 ID=2 の現在の行のフィールド c の値が 0 であると仮定し、更新ステートメントの実行中に、最初のログが書き込まれた後、2 番目のログが書き込まれる前にクラッシュが発生したとします。

  • 最初に REDO ログを書き込み、次に binlog を書き込みます。
    REDO ログが書き込まれたとき、バイナリログが書き込まれる前に、MySQL プロセスが異常に再起動したとします。前に述べたように、REDO ログが書き込まれた後は、システムがクラッシュしてもデータを復元できるため、回復後のこの行の c の値は 1 になります。 ただし、バイナリログが終了する前にクラッシュしたため、この時点ではこのステートメントはバイナリログに記録されませんでした。したがって、後でログをバックアップするときに、このステートメントは保存されたバイナリログには含まれません。 次に、このステートメントのバイナリ ログが失われているため、このバイナリ ログを使用して一時ライブラリを復元する必要がある場合、復元された行の c の値は 0 であることがわかります。元のライブラリの値と同じです。
  • 最初に binlog を書き込み、次に redo ログを書き込みます。
    binlog の書き込み後にクラッシュが発生した場合、REDO ログはまだ書き込まれていないため、クラッシュ回復後のトランザクションは無効になるため、この行の c の値は 0 になります。ただし、binlog には「c を 0 から 1 に変更する」というログが記録されています。したがって、後で binlog を使用して復元すると、復元された行の c の値は 1 になり、元のデータベースの値とは異なります。
    「2 フェーズ コミット」が使用されていない場合、データベースの状態が、そのログを使用して復元されたライブラリの状態と一致しない可能性があることがわかります。

この可能性は非常に低いのではないかと思われるかもしれませんが、一時ライブラリをいつでも復元する必要がある状況はありません。

実際には、いいえ、このプロセスは誤操作後にデータを回復するためだけに必要なわけではありません。容量を拡張する必要がある場合、つまり、システムの読み取り容量を増やすためにさらにバックアップ データベースを構築する必要がある場合、現在の一般的な方法は、完全バックアップを使用し、binlog を適用してこれを実現することです。この「不整合」が原因で問題が発生します。オンラインのマスター データベースとスレーブ データベース間の不一致です。

簡単に言うと、REDO ログと binlog の両方を使用してトランザクションのコミット ステータスを表すことができます。2 段階の提出では、2 つの状態の論理的一貫性が保たれます。