MySQL は INSERT を内部でどう処理しているのか？

[![表紙](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/1f5c578b-6546-4c89-a77c-482cda73c347.png)](https://leapcell.io/?lc_t=q_mysqlhandleinsert)

## 1. 背景：MySQL の基本アーキテクチャ

全体的に見ると、MySQL は大きく分けて Server レイヤーとストレージエンジンレイヤーの 2 つの部分に分かれています。

### Server レイヤー

このレイヤーには、コネクタ、クエリキャッシュ、パーサ、オプティマイザ、エグゼキュータなどが含まれます。たとえば、ストアドプロシージャ、トリガー、ビューなどはこのレイヤーで実装されます。

- **コネクタ（Connection Manager）**：クライアントとサーバー間の接続を処理します。クライアントからのリクエストを受け取り、認証と権限チェックを行い、接続を確立・管理します。
- **クエリキャッシュ（Query Cache）**：古いバージョンの MySQL には存在しますが、新しいバージョンでは推奨されていません。クエリとその結果をキャッシュすることで、パフォーマンスを向上させます。ただし、高負荷環境や大規模データベースでは、ロックやオーバーヘッドの原因となるため、逆にパフォーマンスボトルネックになり得ます。
- **パーサ（Parser）**：SQL クエリ文の文法と意味を解析し、正当性を確認します。SQL 文を内部データ構造に変換し、オプティマイザとエグゼキュータで使用できるようにします。
- **オプティマイザ（Optimizer）**：パーサからのクエリリクエストを受け取り、最適な実行方法を決定します。最良の実行経路、適切なインデックス、結合順序、アクセス手法を選択してクエリ性能を向上させます。
- **エグゼキュータ（Executor）**：オプティマイザが生成した実行プランを実行し、ストレージエンジンからデータを取得してクライアントリクエストを処理します。ストレージエンジンと連携し、クエリを実行して結果をユーザーに返します。

- **ストレージエンジンレイヤー**：データの保存と取得を担当します。MySQL は InnoDB、MyISAM、Memory など複数のストレージエンジンをサポートしています。日常の開発では主に InnoDB が使用されており、MySQL 5.5 以降は InnoDB がデフォルトのストレージエンジンとなっています。

MySQL の基本アーキテクチャを紹介したところで、次に各コンポーネントが、1 つの INSERT SQL に対して何を行うかを見ていきましょう。

## 2. コネクタ

INSERT SQL を実行するには、一般的に MySQL クライアントで接続コマンドを入力し、MySQL サーバーに接続する必要があります。MySQL サーバー側では、コネクタがクライアントと接続を確立し、権限を取得し、接続の維持と管理を担当します。

接続コマンドの例：

```shell
mysql -h(IPアドレス) -P(ポート) -u(ユーザー名) -p
```

接続コマンドを入力した後、正しいパスワードを入力すれば、TCP のハンドシェイクを経て、MySQL サーバーに接続できます。

その後、INSERT SQL を直接入力すると、結果が表示されます。

```shell
mysql> insert into user_score_tab(user_id,score) values(888,10);
Query OK, 1 row affected (0.02 sec)
```

## 3. クエリキャッシュ（Query Cache）

MySQL バージョン 5.6 以前では、接続成功後にクエリキャッシュを使用して SQL クエリのパフォーマンスを最適化します。ただし、テーブルに対する更新や挿入が行われるとキャッシュが無効化されるため、INSERT SQL の実行時にはキャッシュがクリアされます。

実際、MySQL 8.0 以降ではクエリキャッシュは完全に廃止されています。これは、高負荷や大規模なデータベース環境ではクエリキャッシュが逆にパフォーマンス問題の原因となることがあり、実験的にキャッシュを無効にする方が全体的な性能とスケーラビリティが向上することが分かったためです。

## 4. パーサ

INSERT SQL を MySQL サーバーに送信すると、まずそれが何をする命令なのかを解析する必要があります。

そのために、まずはパーサが字句解析（レキシカル解析）を行います。入力された SQL は多数の文字列と空白から構成されていますが、MySQL はそれらの意味をまず分解して解釈します。たとえばこの INSERT SQL：

```sql
insert into user_score_tab(user_id,score) values(888,10);
```

この文では、`insert into` をキーワードとして、`user_score_tab` をテーブル名として、`user_id` と `score` をカラム名として解析します。字句解析の後、文法解析を行います。

文法解析では、その SQL が MySQL の文法に準拠しているかをチェックします。もし文法が誤っていれば、エラーメッセージが表示されます：

```shell
mysql> inser into user_score_tab(user_id,score) value(888,10);
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'inser into user_score_tab(user_id,score) value(888,10)' at line 1
```

字句解析と文法解析が完了すると、それが INSERT SQL であると認識されます。

## 5. オプティマイザ

単純な INSERT SQL に対しては、オプティマイザは複雑なクエリプランの生成を行いません。

オプティマイザはインデックスの選択と維持を行いますが、複雑なクエリ最適化には関与しません。もしテーブルに主キーやインデックスが存在する場合、オプティマイザはデータ整合性を保つためにインデックスの更新を行い、挿入時に制約条件を確認します。

INSERT 文もまた、実行プランを生成します。それは、データベースがどのようにデータへアクセスし、どのインデックスを使用し、どのような順序で処理を行うかを詳細に記述したものです。

## 6. エグゼキュータ

エグゼキュータは具体的な SQL 操作の実行を担当する、データベースシステムの中核モジュールです。INSERT 文においては、エグゼキュータが実際のデータ挿入処理を行います。

1. **挿入位置の決定**：オプティマイザの実行プランに基づいて、データをどの位置に挿入するか（主キーや一意インデックスに基づく位置）を決定します。
2. **データページのロード**：挿入先のデータページがメモリ（Buffer Pool）にあればそれを使用し、なければディスクから対応するデータページをメモリにロードします。
3. **インデックスの更新**：テーブルにインデックス（主キー、ユニークインデックス、その他のインデックス）がある場合、それらも適宜更新されます。

## 7. Buffer Pool

Buffer Pool は MySQL の InnoDB ストレージエンジンにおけるメモリ領域で、データベーステーブルのデータページやインデックスページなどをキャッシュするために使われます。その主な目的は、データの読み書き性能を向上させ、ディスク I/O 操作を減らすことです。

エグゼキュータは新しいデータを Buffer Pool 内の対応するデータページに挿入します。これはメモリ上での操作であり、ディスク上のファイルを直接修正するものではありません。

## 8. undo log

**Undo Log の生成**：実際にデータを挿入する前に、InnoDB は undo log を生成します。INSERT 操作においては、undo log は「現在挿入しようとしているレコードを削除する方法」を記録します（これはトランザクションのロールバック時に使用されます）。

### なぜ undo log を生成するのか？

トランザクションをロールバックする際、MySQL は未コミットの操作を取り消す必要があります。undo log を使うことで、すでに挿入されたがまだコミットされていないレコードを削除し、トランザクションの原子性を保証できます。

## 9. Redo Log

エグゼキュータはデータを挿入した後、その操作を即座に **redo log** に記録します。

データの信頼性を確保するため、MySQL は **WAL（Write-Ahead Logging）** というメカニズムを採用しています。データがディスクに実際に書き込まれる前に、その操作を redo log に先に記録します。

### 処理の流れはどうなっているのか？

MySQL は最初に操作を redo log に書き込み、**プリペア（prepare）** 状態としてマークします。これは、システムがクラッシュしても、redo log を使って操作をリプレイし、データを復旧できることを意味します。

## 10. Binlog への書き込み

**binlog への書き込み**：redo log に書き込まれるのと同時に、MySQL はこの操作を **binlog（バイナリログ）** にも記録します。これはレプリケーションや災害復旧のために使用されます。

binlog は MySQL の論理ログで、SQL 操作の詳細（例：INSERT INTO）を記録します。これは redo log のような物理ログとは異なります。

## 11. トランザクションのコミット（二相コミット）

二相コミット（Two-Phase Commit, 2PC）メカニズムのもとで、MySQL はトランザクションのコミット時に redo log の状態を **commit** に更新します。

### なぜ二相コミットが必要なのか？

**binlog** と **redo log** の整合性を保つためです。もしシステムがクラッシュしても、MySQL は redo log を使って操作をリプレイし、binlog を通じて復旧処理が可能になります。

## 12. データのディスクへの書き込み

エグゼキュータはメモリ内のダーティページ（変更済みページ）をすぐにディスクへ書き込むわけではありません。バックグラウンドスレッドが特定のポリシー（たとえば定期的なフラッシュ）に基づいて、非同期的に Buffer Pool 内のダーティページをディスク上のテーブルスペースファイルに書き込みます。

このようにすることで、頻繁なディスク I/O を回避し、パフォーマンスを向上させることができます。

---

### [私たちはLeapcell、バックエンド・プロジェクトのホスティングの最適解です。](https://leapcell.io/?lc_t=q_mysqlhandleinsert)

[![Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/5175240a-7c3f-4cd9-a292-61549e234a62.png)](https://leapcell.io/?lc_t=q_mysqlhandleinsert)

[Leapcell](https://leapcell.io/?lc_t=q_mysqlhandleinsert)は、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです：

**複数言語サポート**

- Node.js、Python、Go、Rustで開発できます。

**無制限のプロジェクトデプロイ**

- 使用量に応じて料金を支払い、リクエストがなければ料金は発生しません。

**比類のないコスト効率**

- 使用量に応じた支払い、アイドル時間は課金されません。
- 例: $25で6.94Mリクエスト、平均応答時間60ms。

**洗練された開発者体験**

- 直感的なUIで簡単に設定できます。
- 完全自動化されたCI/CDパイプラインとGitOps統合。
- 実行可能なインサイトのためのリアルタイムのメトリクスとログ。

**簡単なスケーラビリティと高パフォーマンス**

- 高い同時実行性を容易に処理するためのオートスケーリング。
- ゼロ運用オーバーヘッド — 構築に集中できます。

[ドキュメントで詳細を確認！](https://docs.leapcell.io/)

[![Try Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/fbeaca2f-567d-4411-880c-6a6c1da42fab.png)](https://leapcell.io/?lc_t=q_mysqlhandleinsert)

Xでフォローする：[@LeapcellHQ](https://x.com/LeapcellHQ)

---

[ブログでこの記事を読む](https://leapcell.io/blog/lifecycle-of-an-insert-statement-in-mysql)