徹底解説シリーズ

中級 MySQL 完全攻略

なんとなく使っている MySQL の中身を、徹底的に解説する

MYSQL INTERMEDIATE DEEP-DIVE
📅 2026 年版MySQL 8.0 / 8.4 LTS初心者〜中級エンジニア向け
このページは一部有料です
Section 01 — 08(G1・G2)は無料で読めます。Section 09 以降(G3 〜 G15)は有料です。
PAID CONTENT
約 3 万字
ABOUT THIS GUIDE

この記事は何をやるか

この記事は初心者から中級のエンジニアを、本番運用に耐えるレベルまで一気に引き上げる徹底解説です。全 57 セクション・15 グループにわたって、MySQL の基礎・アーキテクチャ・InnoDB 内部・MVCC・Redo Log・インデックス・実行計画・レプリケーション・バックアップ・セキュリティ・MySQL 独自機能・8.0 の革新・クラウド運用・エコシステムまでを、図解とインタラクティブツール、そして実際のコマンド例とともに扱います。

MySQL の世界では、「初歩の入門教材」と「すでにベテラン向けの濃すぎる解説」は豊富にあるものの、その間を埋める中級レベルの教材がほとんど存在しません。入門書を読み終えた後、本番運用に出ていく前に何を学べばいいのか分からない — そういう空白を埋めるための記事です。

対象バージョンは MySQL 8.0 と 8.4 LTS です。5.7 は EOL 済みなので、本記事では「廃止された機能」「過去の挙動」として軽く触れる程度に留めます。MariaDB / Percona Server などの派生フォークは扱わず、Oracle MySQL 公式版に絞って解説します。

この記事で身につく力
EXPLAIN FORMAT=TREE / ANALYZE を読んでスロークエリの原因を自力で特定できる
InnoDB の MVCC / Redo Log / Undo Log の挙動を理解して運用判断ができる
innodb_buffer_pool_size / innodb_log_file_size をワークロードに合わせて設計できる
インデックス 4 種を目的別に使い分けられる (B+Tree / Hash / FULLTEXT / Spatial)
Gap Lock / Next-Key Lock 由来のデッドロックを読み解いて回避できる
Binary Log / GTID / Group Replication を構築・運用できる
mysqldump / XtraBackup / PITR を正しい手順で実施できる
ProxySQL / MySQL Router で読み書き分離を設計できる
ユーザー / 権限 / Role / VIEW でセキュアな DB を設計できる
本番運用ミス 15 大パターンを事前に予防できる
対象読者
・MySQL の入門書 / 入門記事を読み終えて、次に何を学ぶべきか迷っている初心者
・SQL は書けるが MVCC や Redo Log の仕組みは把握できていない人
・本番運用しているが「動いている」止まりで、InnoDB の中がブラックボックスな人
・スロークエリやデッドロック、レプリ遅延の原因を自力で追えるようになりたい人
・MySQL を真面目に身につけたいが、ちょうどいい中級レベルの教材が見つからずに困っている人
TABLE OF CONTENTS

目次

全 15 グループの構成です。基礎から順に積み上げていきますが、興味のあるグループから読み始めても OK です。無料有料の境目も合わせて記載しています。

G1
MySQL の全体像と基礎
Section 01 — 02
基礎おさらい・他 RDBMS 比較・データ型・charset/collation・主要 SQL・トランザクション基礎・クエリ実行 5 ステージ概要
無料
G2
アーキテクチャ
Section 03 — 08
SQL Layer と Storage Engine 二層構造・mysqld と Thread・Backend 内部・バックグラウンドスレッド 14 種・メモリ構造・設定ファイル
無料
🔒 ここから有料
G3
テーブル設計とストレージ基礎
Section 09 — 14
テーブル設計・InnoDB Row Format・MVCC・Redo Log / Binary Log・Doublewrite / Change Buffer・Undo と Purge
🔒 有料
G4
クエリ実行と EXPLAIN
Section 15 — 17
5 ステージ詳細・EXPLAIN FORMAT=TREE / JSON / ANALYZE・オプティマイザのコスト計算
🔒 有料
G5
インデックス
Section 18 — 23
4 種比較・B+Tree 内部 (Clustered)・FULLTEXT / Spatial・部分/関数/降順 Index・Invisible Index・OPTIMIZE TABLE / pt-osc
🔒 有料
G6
並行制御
Section 24 — 26
分離レベル 4 種・Gap Lock / Next-Key Lock / Intention Lock・デッドロック (シミュレータ付き)
🔒 有料
G7
パフォーマンスチューニング
Section 27 — 29
Performance Schema / sys schema・slow_query_log / pt-query-digest・innodb_buffer_pool_size / log_file_size 設計
🔒 有料
G8
スケールアウトと高可用性
Section 30 — 34
パーティショニング・Binary Log と GTID Replication・Group Replication・MySQL Router / ProxySQL・InnoDB Cluster HA
🔒 有料
G9
運用
Section 35 — 39
バックアップ全種・mysqlbinlog による PITR・Performance Schema 監視・接続プーリング・アップグレード戦略
🔒 有料
G10
高度な機能
Section 40 — 42
JSON / JSONB と Generated Column・Window 関数 / 再帰 CTE・Stored Routine / Trigger / Event
🔒 有料
G11
セキュリティ
Section 43 — 45
ユーザーと権限・Role (8.0+)・VIEW + Stored Procedure による行制御・SSL/TLS と caching_sha2_password
🔒 有料
G12
よくある運用ミス 15 選
Section 46
本番でやらかしがちな失敗を「何が起き / なぜ / どう直すか」セットで
🔒 有料
G13
MySQL 独自の機能と 8.0 の革新
Section 47 — 52
UPSERT 構文・AUTO_INCREMENT 詳説・sql_mode の罠・TDE と圧縮・Clone Plugin と Shell utilities・X DevAPI / Document Store
🔒 有料
G14
8.0 の SQL 革新とクラウド・エコシステム
Section 53 — 56
Hash Join / Optimizer Trace・Lateral / CHECK 制約 / Invisible Column・Aurora MySQL / RDS / Cloud SQL・Vitess / TiDB / Percona / MariaDB / HeatWave
🔒 有料
G15
締めくくり
Section 57
ここまでのおさらいと、さらに深掘りしたい人への案内
🔒 有料
1
GROUP 1Section 01 — 02
MySQL の全体像と基礎
まず MySQL がどんな DB なのか、その全体像と基礎を押さえます。PostgreSQL や Oracle との立ち位置、MySQL 固有のデータ型と charset/collation、ストレージエンジン (InnoDB / MyISAM) の概念、主要 SQL、トランザクション基礎、そして SQL を投げてから結果が返るまでの 5 ステージ概要までを扱います。
他 RDBMS との比較MySQL 8.x の歴史データ型charset / collationストレージエンジン概念トランザクション基礎クエリ実行 5 ステージ概要
SECTION 01

MySQL の基礎

この章ではまず、MySQL を本格的に深掘りする前に押さえておきたい基礎をおさらいします。「MySQL とは何か」「どんな立ち位置の DB か」「どんな階層構造で、どんなデータ型があり、どうやって操作するか」「トランザクションとは」といった共通言語をここで揃え、次章以降の内部解剖に備えます。すでに知っている内容は読み飛ばしてもらって構いません。

MySQL(マイエスキューエル) は、1995 年にスウェーデンの MySQL AB によって生まれたオープンソースのリレーショナルデータベース管理システムです。Sun Microsystems を経て現在はOracle Corporation が所有・開発しており、Web サービスの DB としては世界で最も広く使われています。LAMP (Linux + Apache + MySQL + PHP) スタックの「M」として 2000 年代に爆発的に普及し、その遺産が今も大きな採用シェアを生み出しています。

MySQL の最大の特徴は、SQL を解釈する「上の階」と、実際にデータを読み書きする「下の階 (ストレージエンジン)」が分離されていることです。同じ SQL でもエンジンを差し替えれば挙動が変わるという、他の RDBMS にない設計です。8.x 時代の事実上の標準はInnoDB で、本記事もこれを前提に解説します。

まずは MySQL が他の RDBMS とどう違うのか、業界での立ち位置から見ていきます。

主要 RDBMS との比較

DB分類強み弱み
MySQL
OSS (Oracle 所有)シェア No.1・運用ナレッジが豊富・シンプル・速い読み取り機能セットは小ぶり・ストレージエンジン選択で挙動が変わる
PostgreSQL
OSS / Object-Relational機能の豊富さ・標準 SQL 準拠・拡張性スキーマレス用途では NoSQL に劣る
Oracle DB
商用 (高額)エンタープライズ機能・サポート・成熟度コスト・複雑さ・ベンダーロックイン
SQLite
OSS / 組込み組込み・軽量・ファイル 1 つ同時書込が苦手・大規模不向き

MySQL が選ばれる場面は明確です。「Web スケール・読み取り中心・運用ナレッジの蓄積」が 重要なシステム —— EC・SNS・CMS・SaaS のバックエンドなど。歴史的に Web 業界の事実上の標準であり、AWS RDS / Aurora MySQL・GCP Cloud SQL のメイン選択肢でもあります。

採用企業の例

Facebook (Meta)
世界最大級の本番 DB
YouTube
初期から MySQL ベース
Uber
バックエンド中核
Airbnb
メイン RDBMS
Twitter / X
長年の本番 DB
Booking.com
予約システム
メルカリ
メイン DB (Aurora MySQL)
GMO ペパボ
主要サービス

MySQL の階層構造 — Server / Database / Table

ここから先を読み進めるにあたって、MySQL の「データの入れ物の階層」を最初に押さえます。MySQL は PostgreSQL とは階層の数が違います。PostgreSQL は Cluster → Database → Schema → Table の 4 層ですが、MySQL は3 層です。さらに「Database = Schema」として扱われるのが MySQL の特徴です。

① MySQL Server (mysqld)(= 1 つの mysqld プロセス・1 つのデータディレクトリ)② Database (= Schema): myapp③ Tablesusersordersproducts③ Views / Routinesv_active_usersget_total()idx_users_email② Database: warehouse③ Tablessales_summaryシステム DBmysqlperformance_schemainformation_schema

各階層の意味は次の通りです。

① MySQL Server (= mysqld)
1 つの mysqld プロセスが管理する全体。物理的には 1 つの MySQL サーバ。データディレクトリ (デフォルト /var/lib/mysql/) に全データベースが格納される。
他 DB の「インスタンス」と同義。1 ホストに複数 mysqld を立てることも可能だがあまりしない。
② Database (= Schema)
サーバ内に複数作れる独立した DB 空間。MySQL では CREATE DATABASECREATE SCHEMA完全に同じ意味。接続時に USE myapp; で 1 つを選ぶ。別 DB のテーブルは db.table で参照可能。
PostgreSQL の Database + Schema が、MySQL ではこの 1 層に潰れている。ここが混乱しやすい点。
③ オブジェクト
データベース内に作られる実体。テーブル / インデックス / ビュー / ストアドプロシージャ / トリガ / イベントなど。
一番触る単位。
システム DB
サーバが自動で持つ特別な DB 群。mysql (権限・ユーザー)・performance_schema (メトリクス)・information_schema (メタデータ)・sys (運用ビュー)。
基本的に直接触らない (mysql.user の直接更新は禁忌)。
PostgreSQL ユーザーへの注意: PostgreSQL の「データベース」は MySQL では存在せず、PostgreSQL の「スキーマ」が MySQL の「データベース」に相当します。MySQL では CREATE SCHEMA = CREATE DATABASE です。ドキュメントで両用語が混在するのはこのためです。

ストレージエンジン — MySQL 最大の特徴

MySQL が他の RDBMS と決定的に違うのが「テーブルごとにストレージエンジンを選べる」点です。SQL を解釈する SQL Layer と、実際のデータ I/O を担う Storage Engine が Handler API で分離されており、エンジンを差し替えるだけで挙動・トランザクション対応・ロック粒度が変わります。

InnoDBRECOMMENDED
本番の標準。トランザクション対応・行レベルロック・クラスタインデックス (B+Tree)・外部キー・クラッシュリカバリ。8.0 ではシステムテーブルも InnoDB 化。迷ったらこれ
MyISAM
5.x 時代の旧デフォルト。トランザクション非対応・テーブルロックのみ。8.0 ではシステムテーブルからも追放され、ほぼ「過去のもの」。新規採用は非推奨。
MEMORY (旧 HEAP)
メモリ上のみに置く一時テーブル。再起動でデータが消える。Hash インデックスが使える。本番用途は限定的 (テンポラリテーブルや集計の中間結果など)。
Archive
高圧縮・INSERT 専用。ログ保管に稀に使う。
CSV
CSV ファイルをそのままテーブルとして扱う。外部ファイル取込時にだけ使う。
NDB Cluster
シェアードナッシング型分散エンジン。MySQL Cluster (NDB) 専用で、通常の MySQL 運用では使わない。
本記事は InnoDB を前提に解説します。 8.x ではシステムテーブルすら InnoDB 化されており、エンジン選択肢は実質「InnoDB 一択」です。MyISAM ・ MEMORY などは「過去にあった」「特殊用途」として軽く触れるに留めます。

ユーザーと権限 — user@host の組で識別

MySQL のアクセス制御は「ユーザー名 + 接続元ホスト」の組で識別されます。PostgreSQL の「ロール」とは設計思想が違い、同じユーザー名でも接続元ホストが違えば別アカウントとして扱われるのが MySQL の独特な点です。

-- ① ユーザー作成 ('user'@'host' の形)
CREATE USER 'alice'@'%' IDENTIFIED BY 'xxx';
-- ↑ どこからでも接続可能な alice

CREATE USER 'alice'@'10.0.0.%' IDENTIFIED BY 'xxx';
-- ↑ 10.0.0.0/24 からのみ接続可能な alice
-- (同じ「alice」でも上とは別のアカウント)

-- ② 権限付与
GRANT SELECT, INSERT, UPDATE ON myapp.* TO 'alice'@'%';

-- ③ Role (MySQL 8.0+) — 権限の束をまとめる
CREATE ROLE 'app_writer';
GRANT SELECT, INSERT, UPDATE ON myapp.* TO 'app_writer';
GRANT 'app_writer' TO 'alice'@'%';
SET DEFAULT ROLE 'app_writer' TO 'alice'@'%';
注意: 8.0 から Role が公式サポートされましたが、「Role を SET DEFAULT ROLE しないと有効化されない」という落とし穴があります。詳しくは Section 43 で扱います。

主要データ型 — まず押さえるべき型

MySQL は SQL 標準型に加え、独自の型を多数持っています。日常的に使う主要なものを分類別にまとめました。

数値型
TINYINT1 byte (-128 〜 127)。フラグ用途
SMALLINT2 byte (-32768 〜 32767)
INT / INTEGER4 byte (約 ±21 億)。最頻出
BIGINT8 byte。サロゲートキーは普通こちら
DECIMAL(p, s) / NUMERIC可変精度。金額計算は必ずこれ (float は誤差)
FLOAT / DOUBLE4 / 8 byte の浮動小数点。誤差あり
文字列型
VARCHAR(n)n 文字までの可変長。本番標準。n は文字数 (byte ではない)
CHAR(n)固定長 (足りない分を空白埋め)。ほぼ使わない
TEXT / MEDIUMTEXT / LONGTEXT64 KB / 16 MB / 4 GB まで。本文用
BINARY(n) / VARBINARY(n)バイナリ列 (byte 数指定)
BLOB / MEDIUMBLOB / LONGBLOBバイナリ可変長 (画像など)
日時型
DATETIME日付+時刻。TZ なし。値をそのまま保存・取出。1000〜9999 年
TIMESTAMP日付+時刻。UTC で保存・取出時にセッション TZ に変換。1970〜2038 年 (2038 年問題に注意)
DATE日付のみ
TIME時刻 / 経過時間 (-838:59:59 〜 838:59:59)
YEAR年 (2 byte)
ブール / その他
BOOLEAN / BOOL実体は TINYINT(1) のエイリアス。true / false も使える
BIT(n)n ビットのビット列
JSONバイナリ JSON。インデックス可 (Generated Column 経由)。5.7 以降の標準
ENUM(...)列挙型 (1 つ選ぶ)。スキーマ変更が面倒なので注意
SET(...)列挙型 (複数選択可)。あまり使わない
GEOMETRY / POINT / POLYGON地理空間データ (SRID 対応・R-Tree インデックス可)

charset と collation — utf8mb4 と utf8 の罠

MySQL の文字コード回りは歴史的に最も事故が多いトピックです。「utf8」と「utf8mb4」の違いを正しく理解していないと、本番投入後に絵文字が入らない・四文字漢字 (𠮷野家など) が化ける、という事故を起こします。

utf8mb4
真の UTF-8 (1〜4 byte)。絵文字・サロゲートペア漢字 OK。8.0 のデフォルト。本番はこれ一択。
utf8 (= utf8mb3)
偽の UTF-8 (1〜3 byte 制限)。MySQL 独自の歴史的負債。絵文字・𠮷などが入らない。8.0+ で非推奨に降格。
latin1
5.x 時代の旧デフォルト。マルチバイト不可。レガシー DB でまだ見かける。

collation (照合順序) は文字列の比較・ソート規則です。デフォルトは utf8mb4_0900_ai_ci (8.0+) で、大文字小文字とアクセントを区別しません。区別したい場合は utf8mb4_0900_as_cs (Accent-Sensitive Case-Sensitive) などを使います。

-- ① 推奨: utf8mb4 + 大小無視の collation
CREATE DATABASE myapp
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_0900_ai_ci;

-- ② テーブル単位で指定する場合
CREATE TABLE messages (
  id BIGINT PRIMARY KEY,
  body TEXT
) ENGINE=InnoDB
  DEFAULT CHARSET=utf8mb4
  COLLATE=utf8mb4_0900_ai_ci;

-- ③ サーバ全体のデフォルトを確認
SHOW VARIABLES LIKE 'character_set_%';
SHOW VARIABLES LIKE 'collation_%';
事故防止チェック: SHOW CREATE TABLE で文字コード・collation を必ず確認。アプリ接続側の SET NAMES utf8mb4 を忘れると、せっかく utf8mb4 のテーブルでもクライアントとの間で文字化けします。

mysql クライアントの基本 — 接続とメタコマンド

mysql は MySQL の標準対話クライアントです (PostgreSQL の psql に相当)。SQL 実行に加え、独自のメタコマンド (バックスラッシュコマンド) と SHOW 文で DB の状態を素早く確認できます。

# === 接続方法 ===
# 個別オプション
mysql -h db.example.com -P 3306 -u alice -p myapp
#                       ↑ ポート (デフォルト 3306)

# Unix ソケット (ローカル)
mysql -u root -p

# 環境変数
export MYSQL_PWD=xxx
mysql -h db.example.com -u alice myapp

# === よく使うメタコマンド ===
\?              -- ヘルプ
\h              -- 同上
\q              -- 終了
\c              -- 入力中の SQL をキャンセル
\g              -- ; の代わり (実行)
\G              -- 縦表示で実行 (1 カラム 1 行)

# === SHOW 文 (PostgreSQL のメタコマンドに相当) ===
SHOW DATABASES;                 -- DB 一覧 (= \l in psql)
USE myapp;                      -- DB 切替 (= \c in psql)
SHOW TABLES;                    -- 現 DB のテーブル一覧 (= \dt)
SHOW TABLES FROM warehouse;     -- 別 DB のテーブル一覧
DESC users;                     -- カラム一覧 (= \d in psql)
DESCRIBE users;                 -- 同上
SHOW CREATE TABLE users;        -- CREATE 文を再構成
SHOW INDEX FROM users;          -- インデックス一覧
SHOW PROCESSLIST;               -- 現在の接続一覧 (= pg_stat_activity)
SHOW STATUS LIKE 'Threads%';    -- ステータス変数
SHOW VARIABLES LIKE 'innodb%';  -- 設定変数

# === 設定 ===
\T /tmp/out.txt -- 出力をファイルへ (tee)
\.              -- ファイル実行 (= source)
source schema.sql

# === エクスポート ===
SELECT * FROM users
  INTO OUTFILE '/tmp/users.csv'
  FIELDS TERMINATED BY ',' ENCLOSED BY '"'
  LINES TERMINATED BY '\n';

覚えるべき必須コマンド: SHOW TABLESDESC tableSHOW CREATE TABLESHOW PROCESSLISTSHOW VARIABLES\G (縦表示)。これだけで本番調査の 80% が回ります。

接続文字列の書き方と接続まわりの設定

アプリ / ドライバが MySQL に接続するときの接続文字列 (URI) の書き方を整理します。「同じ DB なのにアプリによって接続方法が違う」の混乱を防ぐ知識です。

# ① mysql:// (Classic Protocol — port 3306)
mysql://alice:xxx@db.example.com:3306/myapp?ssl-mode=REQUIRED

# ② mysqlx:// (X Protocol — port 33060)
mysqlx://alice:xxx@db.example.com:33060/myapp

# ③ JDBC (Connector/J)
jdbc:mysql://db.example.com:3306/myapp?\
  useSSL=true&\
  serverTimezone=UTC&\
  characterEncoding=utf8mb4&\
  autoReconnect=false&\
  useServerPrepStmts=true

# ④ Python (mysql-connector-python)
mysql+mysqlconnector://alice:xxx@db.example.com:3306/myapp

# ⑤ Go (go-sql-driver/mysql)
alice:xxx@tcp(db.example.com:3306)/myapp?parseTime=true&loc=UTC

# ⑥ Unix socket (ローカル / RDS Proxy 等)
mysql://alice:xxx@localhost/myapp?unix_socket=/var/run/mysqld/mysqld.sock

# ⑦ クエリパラメータでよく指定するもの
#   ssl-mode=REQUIRED / VERIFY_CA / VERIFY_IDENTITY
#   charset=utf8mb4 / characterEncoding=utf8mb4
#   timezone=UTC / serverTimezone=UTC
#   connectTimeout=10000 (10秒)
#   socketTimeout=300000 (5分・クエリの最大実行時間)

本番接続まわりの落とし穴 — skip_name_resolve と back_log

大量接続のアプリで 「接続が急に遅くなる」 問題の 9 割は次の 2 つで解決します。

# ① skip_name_resolve — DNS 逆引きをスキップ
#    アプリが接続するたびに mysqld が DNS 逆引きでホスト名を取りに行く
#    DNS が遅いと接続が数秒詰まる → 本番では必須
[mysqld]
skip-name-resolve = ON
# ↑ ON にすると 'user'@'host' の host 部はホスト名ではなく IP のみで判定される
# → mysql.user の host 列は IP / IP マスクで定義する必要

# ② back_log — accept キューの深さ
#    バースト的に大量接続が来たときに mysqld が accept しきれない
#    OS の listen() backlog を超えて新規接続が ECONNREFUSED
back_log = 4096  # デフォルト 80 〜 901 (max_connections+50)

# 確認
SHOW VARIABLES LIKE 'skip_name_resolve';
SHOW VARIABLES LIKE 'back_log';

# ③ bind_address — どの IF で listen するか
#    デフォルト 8.0 → * (全 IF)
#    特定 IP のみで listen したいなら指定
bind_address = 0.0.0.0   # 全 IF
bind_address = 10.0.0.5  # 特定 IF
本番チェック: SHOW PROCESSLIST で State 欄に loginchecking permissions が長く出ているなら DNS 逆引き疑い。skip_name_resolve=ON を真っ先に確認。

主要 SQL コマンド一覧 — DDL / DML / TCL / DCL

SQL コマンドは大きく 4 種類に分類されます。これからの章でも頻出する用語なので、ここで整理しておきます。

DDL (Data Definition Language) — 構造を定義
CREATE TABLEテーブルを作る
ALTER TABLEテーブル定義を変更 (列追加・型変更・index 追加など)。8.0 は Atomic DDL
DROP TABLEテーブルを削除する
CREATE INDEXインデックスを作る
CREATE / DROP DATABASEDB を作る・削除する
TRUNCATE TABLE全行高速削除 (DDL 扱い、ロールバック不可)
DML (Data Manipulation Language) — データを操作
SELECTデータを取得する。最頻出
INSERT行を追加する
UPDATE行を更新する
DELETE行を削除する
LOAD DATA INFILE大量データを高速取込 (PostgreSQL の COPY に相当)
REPLACE / INSERT ... ON DUPLICATE KEY UPDATEUPSERT (存在すれば更新)
TCL (Transaction Control Language) — トランザクション制御
BEGIN / START TRANSACTIONトランザクション開始
COMMIT変更を確定
ROLLBACK変更を取り消し
SAVEPOINT / RELEASE SAVEPOINTトランザクション内に戻り点を作る
SET TRANSACTION分離レベル等を設定
DCL (Data Control Language) — 権限制御
GRANT権限を付与
REVOKE権限を剥奪
CREATE / DROP USERユーザー追加・削除
CREATE / DROP ROLERole 追加・削除 (8.0+)

トランザクションの基本 — ACID と BEGIN / COMMIT / ROLLBACK

トランザクションは、複数の SQL 文を「全部成功するか、全部なかったことにする」 1 つの単位として扱う仕組みです。本番運用で最も基本的かつ重要な概念のひとつ。MySQL ではInnoDB エンジンでのみトランザクションが使えます。MyISAM はトランザクション非対応です。

InnoDB のトランザクションは、ACID の 4 性質を満たします。

AAtomicity (原子性)トランザクション内の SQL は「全部実行 or 全部取り消し」。途中で止まることはない。
CConsistency (一貫性)制約 (PK / FK / UNIQUE / CHECK) を破る変更はコミットされない。整合性が保たれる。
IIsolation (分離性)並行する他のトランザクションの中途半端な状態は見えない。分離レベルで制御 (Section 24 で深掘り)。
DDurability (永続性)COMMIT した変更はクラッシュしても失われない。Redo Log がこれを保証 (Section 12 で深掘り)。

基本的な書き方は次の通りです。

-- ① 普通のトランザクション
START TRANSACTION;
  UPDATE accounts SET balance = balance - 100 WHERE id = 'alice';
  UPDATE accounts SET balance = balance + 100 WHERE id = 'bob';
COMMIT;
-- 両方成功してから確定。途中エラーなら何も変わらない

-- ② 失敗時の取消
START TRANSACTION;
  UPDATE orders SET status = 'shipped' WHERE id = 1;
  -- ↑ ここでエラー or やっぱり取消したい
ROLLBACK;
-- → 何も変わらなかったことになる

-- ③ 自動コミット (デフォルト挙動)
UPDATE accounts SET balance = balance - 100 WHERE id = 'alice';
-- ↑ これ単体で 1 トランザクションとして即コミット
-- (autocommit=1 がデフォルト)

-- 自動コミットを切ると、COMMIT を打つまで確定しない
SET autocommit = 0;
UPDATE ...;  -- まだコミットされていない
COMMIT;       -- ここで確定

-- ④ 安全な実験 (本番で危険な UPDATE を試したい時)
START TRANSACTION;
  UPDATE users SET deleted_at = NOW() WHERE created_at < '2020-01-01';
  SELECT COUNT(*) FROM users WHERE deleted_at IS NOT NULL;
  -- ↑ 影響件数を確認
ROLLBACK;
-- → 結果だけ見て、本当は何もしなかったことにできる
注意: TRUNCATE TABLEALTER TABLECREATE TABLE などの DDL は、内部で暗黙コミットされます。トランザクション中に DDL を混ぜると、それまでの DML が予期せず確定するので注意。8.0 でAtomic DDL が入りましたが、トランザクション境界の挙動は変わっていません。
この記事のゴール:初心者から中級のエンジニアが「本番運用で起きる問題の 9 割を自力で解決できる」状態を目指します。 ここまで押さえた基礎の上に、InnoDB の MVCC・Redo Log・実行計画・インデックスといった、Web 上の入門記事では深く触れない内部の仕組みを 徹底的に解説していきます。
SECTION 02

クエリが実行されるまで

SELECT 文を投げてから結果が返ってくるまで、MySQL は内部で 5 つのステージ を順に通過しています。Parse → Resolve → Optimize → Execute → Output の流れです。それぞれが何をやっているかをざっくり押さえておくと、後の章 (EXPLAIN・Optimizer・実行プラン) が一気に理解しやすくなります。

ここでは全体の流れを俯瞰します。詳細は G4 (Section 15〜17) で深掘りします。

クエリ実行の 5 ステージ

ClientSTAGE 1Parse(構文解析)STAGE 2Resolve(名前解決)STAGE 3Optimize(最適化)STAGE 4Execute(実行)STAGE 5Output(結果送信)結果が返る
STAGE 1Parse (構文解析)
何をする
SQL 文字列を字句解析・構文解析してパースツリー (AST) に変換する。
入出力
SELECT * FROM users WHERE id=1 → AST
失敗例
構文エラーはここで弾かれる (You have an error in your SQL syntax)
STAGE 2Resolve (名前解決)
何をする
テーブル名・列名・関数名を実在のオブジェクトに紐づける。権限チェック・暗黙の型変換解決もここ。
入出力
AST + データディクショナリ → 解決済みツリー
失敗例
Unknown column 'X' in field list や権限不足はここ
STAGE 3Optimize (最適化)
何をする
実行計画を作る最も重要なステージ。テーブルのアクセス順・index 選択・join 方式を決定する。
入出力
解決済みツリー → 実行プラン (EXPLAIN で見える)
失敗例
統計情報が古いと変なプランが選ばれる (要 ANALYZE TABLE)
STAGE 4Execute (実行)
何をする
プランに従って Storage Engine (InnoDB) からデータを取り出す。Buffer Pool / index B+Tree を実際に辿る
入出力
Handler API 経由で InnoDB を呼び出し
失敗例
Lock 待ち・デッドロック・disk I/O はここで発生
STAGE 5Output (結果送信)
何をする
結果セットをネットワーク経由でクライアントへ送る。大量行は分割して送信される。
入出力
rows → クライアントへ
失敗例
タイムアウトやネットワーク切断はここ
EXPLAIN で見えるのはステージ 3 (Optimize) の結果です。 「なぜ遅いか」を調べるとき、最初に見るべきは optimizer がどんなプランを選んだか。EXPLAIN・EXPLAIN ANALYZE の読み方は Section 16 で深掘りします。
5.7 までは Query Cache がありました。 「同じ SQL の結果をメモリに保持して 2 回目以降を高速化する」という仕組みでしたが、並行性問題でほぼ常に有害だったため 8.0 で完全に廃止 されました。今は SELECT SQL_CACHEquery_cache_type も無効です。

ステージ別の典型エラー — エラーコードで原因を切り分ける

MySQL のエラーコードは「どのステージで起きたか」を教えてくれます。エラーを見たら、まずどのステージで止まったかを判別すると原因究明が速い。

ER_PARSE_ERROR (1064)① Parse
SQL: SELECT * FROM users WHRE id=1;
ERR: You have an error in your SQL syntax near "WHRE id=1"
FIX: タイポ / 予約語の使用 / セミコロン抜け
ER_BAD_FIELD_ERROR (1054)② Resolve
SQL: SELECT mail FROM users;
ERR: Unknown column 'mail' in 'field list'
FIX: カラム名を確認 (email だった)
ER_NO_SUCH_TABLE (1146)② Resolve
SQL: SELECT * FROM userss;
ERR: Table 'myapp.userss' doesn't exist
FIX: テーブル名を確認
ER_TABLEACCESS_DENIED_ERROR (1142)② Resolve
SQL: SELECT * FROM mysql.user;
ERR: SELECT command denied to user 'app'@'%' for table 'user'
FIX: 権限不足 — GRANT で付与
ER_LOCK_WAIT_TIMEOUT (1205)④ Execute
SQL: UPDATE ... WHERE id=1;
ERR: Lock wait timeout exceeded; try restarting transaction
FIX: 別 TX のロック競合 — Section 25/26
ER_LOCK_DEADLOCK (1213)④ Execute
SQL: UPDATE ... (循環 LOCK)
ERR: Deadlock found when trying to get lock
FIX: アプリでリトライ — Section 26
ER_DUP_ENTRY (1062)④ Execute
SQL: INSERT INTO users (email) VALUES ('a@e.com');
ERR: Duplicate entry 'a@e.com' for key 'users.email'
FIX: UNIQUE 違反 — ON DUPLICATE KEY UPDATE 検討 (Section 47)
ER_NO_REFERENCED_ROW (1452)④ Execute
SQL: INSERT INTO orders (user_id) VALUES (999);
ERR: Cannot add or update a child row: a foreign key constraint fails
FIX: FK 違反 — 親テーブルに存在しない ID
ER_NET_PACKET_TOO_LARGE (1153)⑤ Send
SQL: 巨大 BLOB を取得
ERR: Got a packet bigger than 'max_allowed_packet'
FIX: max_allowed_packet を上げる (サーバ + クライアント両方)
ER_CON_COUNT_ERROR (1040)接続
SQL: (接続時)
ERR: Too many connections
FIX: max_connections + プール戦略 — Section 38
2
GROUP 2Section 03 — 08
アーキテクチャ
「mysqld って何のプロセス?」「InnoDB Buffer Pool って結局どこにあって何をしているの?」を解決する章。MySQL は SQL Layer と Storage Engine の二層構造になっており、その境界 (Handler API) で何が起きているか、どんなスレッドが背後で動いているか、どんなメモリ領域が使われているか、そしてそれらの挙動を制御する設定ファイルまでを扱います。
SQL Layer + Storage Enginemysqld と ThreadBackend 内部バックグラウンドスレッドメモリ構造設定ファイル
SECTION 03

アーキテクチャ概要

MySQL の内部アーキテクチャを最初に俯瞰します。SQL Layer (上の階)Storage Engine Layer (下の階)Handler API で分離された二層構造 ── これが MySQL を他の RDBMS と決定的に分ける設計です。

この章では、まず全体図でどんな部品が動いているかを把握し、次章以降 (mysqld・Backend Thread・Background Thread・メモリ・設定) で各部品を深掘りしていきます。

MySQL の全体アーキテクチャ図

Clientsmysql CLIApp (Connector/J)App (mysqli)ProxySQLMySQL WorkbenchCONNECTION LAYER認証 (caching_sha2_password) / SSL / Thread per ConnectionConnection PoolThread ManagerSQL LAYER (上の階)共通: どのエンジンでも同じ動きをする部分ParserSQL→ASTResolver名前解決Optimizerプラン作成Executorプラン実行Cache (binlog 等)binlog/generalMgmt Services権限/イベントHandler API(SQL Layer と Storage Engine の境界 — どのエンジンでも同じ呼び出し)例: ha_innodb::index_read(), ha_innodb::write_row(), ha_innodb::external_lock()STORAGE ENGINE LAYER (下の階)テーブル毎に選べる: ENGINE=InnoDBInnoDB本番標準・TX/FK/Row Lock★ 8.x 標準MyISAM5.x の旧 defaultMEMORY一時テーブルArchive圧縮 INSERT 専用CSVCSV 直接NDBクラスタ専用📀 Disk: データファイル (.ibd) / Redo Log / Undo / Binary Log / Slow Log / Error Log

二層構造のメリット — 同じ SQL がエンジンで挙動を変える

二層構造の最大のメリットは、SQL Layer を変えずに「データの保存方法だけ」を差し替えられることです。同じ SELECT * FROM users WHERE id=1 でも、テーブルに付けたエンジンによって挙動が変わります。

観点InnoDBMyISAMMEMORY
トランザクション✓ ACID 対応✗ 非対応✗ 非対応
ロック粒度行レベル (Row Lock)テーブルレベルのみテーブルレベル
外部キー
インデックスClustered B+TreeB-Tree + HeapHash / B-Tree
クラッシュリカバリ✓ Redo + Undo✗ 手動修復データ消失
永続化ディスクディスクメモリのみ
用途本番標準レガシー / 読み専用一時テーブル / 集計中間

クエリが実行される流れ — どこで何が起きるか

一つの SELECT 文が投げられたとき、各層で何が起きるかを順に見てみます。これを把握しておくと、Section 16 で扱う EXPLAIN の出力が「どこのステージで何を計画しているか」として読めるようになります。

SELECT name, email FROM users WHERE id = 42;
                                 │
   ┌─────────────────────────────┴──────────────────────────────┐
   │ ① Connection Layer                                           │
   │   接続スレッドが受信。認証済みであることを確認。               │
   │   → SQL 文字列をパーサーに渡す                                  │
   ├──────────────────────────────────────────────────────────────┤
   │ ② SQL Layer — Parser                                          │
   │   字句解析 + 構文解析 → パースツリー (AST)                       │
   ├──────────────────────────────────────────────────────────────┤
   │ ③ SQL Layer — Resolver                                        │
   │   "users" → 実テーブルにバインド                                │
   │   権限チェック (alice は users を読めるか?)                      │
   ├──────────────────────────────────────────────────────────────┤
   │ ④ SQL Layer — Optimizer                                       │
   │   index 候補を列挙: PRIMARY (id) ← これが最良                    │
   │   コスト計算: PK 直引きが最安 → eq_ref                            │
   │   → 実行プラン完成 (EXPLAIN で見えるのはここ)                    │
   ├──────────────────────────────────────────────────────────────┤
   │ ⑤ SQL Layer — Executor                                        │
   │   Handler API を呼ぶ:                                          │
   │     handler->index_read(buf, key=42, ...)                     │
   ├──────────────────────────────────────────────────────────────┤
   │ ⑥ Storage Engine Layer — InnoDB                               │
   │   B+Tree の root から id=42 を二分探索                          │
   │   Buffer Pool に該当ページがあれば即返却 (HIT)                    │
   │   なければディスクから 16KB Page を読込 → Buffer Pool に乗せる      │
   │   → 行データを SQL Layer に返す                                  │
   ├──────────────────────────────────────────────────────────────┤
   │ ⑦ SQL Layer — Executor (続き)                                  │
   │   返ってきた行から name, email を投影                             │
   │   結果セットを組み立てる                                          │
   ├──────────────────────────────────────────────────────────────┤
   │ ⑧ Connection Layer                                            │
   │   結果セットをネットワーク経由でクライアントへ送信                  │
   │   完了ステータスを返す                                            │
   └──────────────────────────────────────────────────────────────┘

MySQL のディスクファイル — 何がどこに保存されるか

上記アーキテクチャの「📀 Disk」に置かれるファイルを整理します。データディレクトリ (デフォルト /var/lib/mysql/) には次のファイル群があります。

ibdata1
システムテーブルスペース。5.x ではここに全テーブルが入っていた。8.0+ ではメタデータと undo の一部のみ。
*.ibd (例: users.ibd)
テーブルごとのデータファイルinnodb_file_per_table=ON (デフォルト) で 1 テーブル = 1 ファイル。データと secondary index が同居。
ib_logfile0, ib_logfile1
InnoDB Redo Log。コミット時の永続化を担う (Section 12)。サイズは innodb_log_file_size で制御。
undo_001, undo_002
Undo Tablespace (8.0+)。MVCC の旧バージョンを保持 (Section 11/14)。長時間 TX で肥大化しがち。
binlog.000001, binlog.000002, ...
Binary Log。レプリケーション + PITR 用 (Section 12/31)。書き込みごとに増える。
mysql-slow.log
スロークエリログlong_query_time 秒以上かかった SQL を記録 (Section 28)。
mysql-error.log
エラーログ。起動・停止メッセージ・致命的エラー。最初に見るログ。
mysql.sock
Unix ソケットファイル。ローカル接続用。
Tip: SHOW VARIABLES LIKE 'datadir'; で現在のデータディレクトリが分かります。SHOW VARIABLES LIKE 'innodb_data_file_path'; で ibdata1 のサイズと autoextend 設定を確認できます。
この章で押さえる「3 つの境界」:① クライアントとサーバ (mysqld) を分ける ネットワーク境界、 ② SQL Layer と Storage Engine を分ける Handler API 境界、 ③ Storage Engine とディスクを分ける I/O 境界。 どの「境界」を越えるかでパフォーマンスとロックの挙動が大きく変わります。チューニングの議論はだいたいこの 3 境界のどれかの話です。
SECTION 04

mysqld と Connection / Thread

MySQL のサーバ実体は mysqld という 1 プロセスです。PostgreSQL のように「接続ごとに新しいプロセス」が立ち上がるのではなく、mysqld の中に「接続ごとのスレッド」が走るモデルです。これをThread per Connection方式と呼びます。

この方式は 軽量(プロセス作成より速い)・メモリ共有が容易(プロセス間通信が不要) という利点があります。一方で、1 スレッドが暴走すると全体に影響しやすい、という弱点もあります。

Thread per Connection の動作

mysqld プロセス (1 つ)Listenerport 3306 / accept()Thread Cache遊休スレッドを保持Thread #1001alice@%Query (SELECT)Thread #1002bob@%SleepThread #1003app@%LockedThread #1004app@%Query (UPDATE)Thread #1005app@%Sending dataGlobal 共有メモリBuffer PoolRedo Log BufferChange BufferAdaptive Hash IndexBackground ThreadsMasterPage CleanerPurgeIO ReadIO WriteApp #1App #2mysql CLIProxySQLCLIENTS
Listener
TCP port 3306 (デフォルト) と Unix Socket を listen。新規接続が来ると accept() してスレッドを割り当てる
Thread Cache
接続が切れたスレッドをすぐ捨てず保持しておく仕組み。新規接続時に再利用 (作成コスト削減)。サイズは thread_cache_size
Connection Thread
1 つの接続 = 1 スレッド。状態は SHOW PROCESSLIST で確認可能 (Sleep / Query / Locked / Sending data など)
Global 共有メモリ
全スレッドで共有。Buffer Pool が中心 (Section 07)
Background Threads
クライアント接続とは独立に常時動作。チェックポイント・purge・I/O 処理 (Section 06)

接続ライフサイクル — TCP から Query 実行まで

1 つのクライアントが接続してクエリを投げて切断するまでのライフサイクルを順に見ます。各ステップが SHOW PROCESSLIST の State 欄に対応しているので、本番調査で何が起きているかが読めるようになります。

Step 1 — TCP 接続
  App → mysqld:3306 への TCP connect()
  ↓
Step 2 — Thread 割り当て
  Listener が accept() → Thread Cache から取り出す or 新規 spawn
  ↓
Step 3 — 認証
  State: "Login"
  Client が user/password を送信 → mysqld が caching_sha2_password で照合
  失敗 → 接続拒否、成功 → セッション確立
  ↓
Step 4 — Idle (待機)
  State: "Sleep"
  接続は確立しているが、まだクエリを受け取っていない状態
  ↓
Step 5 — Query 受信
  State: "Query" → "Reading from net"
  クライアントから SQL 文字列を受信
  ↓
Step 6 — Parse / Resolve
  State: "starting" → "checking permissions"
  ↓
Step 7 — Optimize / Execute
  State: "Sending data" / "Sorting result" / "Locked" など
  Optimizer がプラン作成 → Executor が InnoDB へ
  ↓
Step 8 — 結果送信
  State: "Sending data"
  ↓
Step 9 — 完了
  State: "Sleep" に戻る
  ↓ (もう一度 Step 5 へ — 接続を使い回す)

(接続終了)
Step 10 — Disconnect
  mysql_close() → スレッドは Thread Cache に戻る (or 終了)
SHOW PROCESSLIST の State を読めると本番調査が変わります。 「Sending data 」は実はクエリ実行中全般を意味し、必ずしも「データを送信中」ではない、というのが古典的ハマりポイント。8.0 では Performance Schema を見るのが正確 (performance_schema.events_statements_current)。

max_connections と接続数設計

mysqld が同時に保持できる接続数の上限は max_connections で決まります (デフォルト 151)。これを超えると新規接続は Too many connections エラーで弾かれます。

単に大きくすれば良いというものではなく、1 接続あたりのメモリ消費 × max_connections がサーバの実メモリを圧迫します。本番設計の式は以下:

物理メモリ
  ≥ innodb_buffer_pool_size              # ① Global バッファ
  + その他 Global バッファ (Log/Change 等)   # ② Global バッファ
  + max_connections × per-thread メモリ   # ③ Session バッファ
  + OS / その他

per-thread メモリ ≈
    sort_buffer_size
  + join_buffer_size
  + read_buffer_size
  + read_rnd_buffer_size
  + thread_stack
  + tmp_table_size (上限)
  + binlog_cache_size
  + net_buffer_length
  ≒ 1〜4 MB が典型 (デフォルト)

例: max_connections=500, per-thread=4 MB → Session 合計 2 GB
    Buffer Pool 12 GB を使うなら、物理 16 GB は最低欲しい

本番で max_connections を闇雲に上げるのは禁忌です。アプリ側でコネクションプール (HikariCP / ProxySQL / MySQL Router) を組み、サーバ側は適正な上限を保つのが原則。詳細は Section 38 で扱います。

max_connections
500
同時接続の上限
thread_cache_size
50
遊休スレッドの再利用数
wait_timeout
28800 (8h)
Sleep 状態の接続を自動切断するまでの秒数

Thread Pool — Enterprise 版の選択肢

1 接続 = 1 スレッドの方式は、接続数が数千を超えるとコンテキストスイッチコストが無視できなくなります。これを解決するのが Thread Pool です。

Thread per Connection (デフォルト)
・1 接続 = 1 OS スレッド
・無料・Community 版で使える
・接続数 〜数百 程度まで現実的
・実装が単純で挙動が読みやすい
Thread Pool (Enterprise / Percona)
・固定数のワーカースレッドが多数の接続を捌く
・MySQL Community には無い機能
・Percona Server / MariaDB には類似機能あり
・1 万接続規模を狙うとき有効
Community 版で 1 万接続を捌きたい場合は、Thread Pool ではなく ProxySQL や MySQL Router でアプリ側のプール と組み合わせるのが現実的です。MySQL 本体への接続数は数百に抑え、プロキシで大量接続を吸収します。

挙動を絵で比較 — 同じ 8 接続を捌くとき

Thread per Connection1 接続 = 1 OS スレッド (Community 版)Conn #1Conn #2Conn #3Conn #4Conn #5Conn #6Conn #7Conn #8OS Threads (8 個)Thread 1000Thread 1001Thread 1002Thread 1003Thread 1004Thread 1005Thread 1006Thread 10071 万接続なら 1 万 OS スレッド→ コンテキストスイッチ爆発→ メモリ消費 (1 接続 ~17MB)○ シンプル・予測可能Thread Pool (Enterprise / Percona)固定ワーカースレッドで多数の接続を捌くConn #1Conn #2Conn #3Conn #4Conn #5Conn #6Conn #7Conn #8Thread Pool Dispatcher (タスクキュー)Worker Threads (3 個・グループ毎)Worker 2000Worker 2001Worker 20021 万接続でも数十ワーカー○ コンテキストスイッチ少○ メモリ消費激減× 長時間 TX で詰まりやすい
Thread Pool の罠: 長時間ロック待ちや遅いクエリがワーカーを専有すると、他の接続が処理待ちになって全体が止まる。クエリのレイテンシが揃っているワークロードでこそ威力を発揮します。OLTP の短いクエリが大量に来る用途が前提。
この章のまとめ:① mysqld は 1 プロセス + 多数のスレッドで動く。 ② 1 接続 = 1 スレッド (Thread per Connection)。 ③ max_connections × per-thread メモリでサーバメモリを圧迫しないこと。 ④ 大量接続は ProxySQL 等のプール層で吸収する。
SECTION 05

Backend スレッドの内部

1 つの接続スレッドが、SQL を受け取ってから結果を返すまでに内部でどんな段階を踏んでいるかを追います。Parser → Resolver → Optimizer → Executor → Handler → Storage Engine という流れと、それぞれが触るデータ構造を順に見ていきます。

この章のゴールは「EXPLAIN がどのステージの結果を表示しているのか」「本番でクエリが遅いとき、どのステージの問題なのか」を分けて考えられるようになることです。

Backend スレッドの内部パイプライン

Receive
Network から SQL 文字列を受け取る
触るメモリnet_buffer (Session)
Parser
字句解析 (Lexer) → 構文解析 (yacc/bison) → AST
触るメモリthd->mem_root (Session)
Resolver
名前解決 / 権限チェック / VIEW 展開 / Prepared Statement キャッシュ照合
触るメモリData Dictionary (Global)
Optimizer
index 候補列挙 → コスト計算 → 実行プラン決定
触るメモリoptimizer_trace (Session, 任意)
Executor
プランを iterator として実行。
NestedLoop / Hash / Sort / Aggregate を組み合わせる
触るメモリsort_buffer / join_buffer / tmp_table (Session)
Handler
Handler API でストレージエンジンを呼ぶ (handler::index_read 等)
触るメモリ(境界)
Storage Engine
InnoDB が Buffer Pool / B+Tree を辿って行を返す
触るメモリBuffer Pool (Global)
Send
結果セットをパケット化して Network へ送信
触るメモリnet_buffer (Session)

各ステージの詳細

② Parser
SQL 文字列を Lexer (字句解析) でトークン列に変換し、Bison ベースの Parser (構文解析)抽象構文木 (AST) を作る。
コスト
文字列処理だけ・I/O なし・通常 1ms 未満
失敗例
You have an error in your SQL syntax 系のエラーはここ
観察方法
parser-time は SHOW STATUSSlow_queries には現れない (実質ゼロ)
③ Resolver
AST 内のテーブル名・カラム名・関数名を実体に紐づける。権限チェックVIEW 展開Prepared Statement キャッシュ照合もここ。
コスト
Data Dictionary を引く・通常 1ms 未満
失敗例
Unknown column / SELECT command denied はここ
観察方法
Performance Schema の events_statements_history で計測可能
④ Optimizer
最も重要なステージ。複数のテーブルアクセス順 (Join Order)・index 選択・Join 方式 (Nested Loop / Hash Join / Block Nested Loop)・ソート方式を組み合わせ、コスト最小のプランを選ぶ。
コスト
クエリの複雑さに比例 (Join が多いと指数的)
失敗例
統計情報が古い → 想定外のプラン (要 ANALYZE TABLE)
観察方法
EXPLAIN / EXPLAIN FORMAT=JSON / optimizer_trace で観察可能 (Section 16)
⑤ Executor
Optimizer が決めたプランを iterator (Volcano モデル) として実行する。8.0+ では各ノードが Iterator::Read() を実装する設計。NestedLoop / Hash / Sort / Filter / Aggregate のノードが連なって木構造になる。
コスト
実プランの I/O とメモリ使用が支配的
失敗例
sort_buffer 不足 → ディスクソート / tmp_table 不足 → MyISAM/InnoDB 一時テーブル
観察方法
EXPLAIN ANALYZE (8.0.18+) で実測 (Section 16)
⑥ Handler
SQL Layer から Storage Engine への境界ha_innodb クラスが InnoDB 用の Handler を実装している。基本 API: index_init / index_read / index_next / rnd_init / rnd_next / write_row / update_row / delete_row / external_lock
コスト
関数呼び出しオーバーヘッドのみ (微小)
失敗例
API レベルの問題は稀
観察方法
gdb で ha_innodb::index_read にブレークポイントを置いて観察 (開発時)
⑦ Storage Engine (InnoDB)
Handler 呼び出しを受けて、Buffer Pool に該当 Page があればメモリヒット、なければディスク I/O。B+Tree を辿って行を返す。Lock もここで取る (Section 25)。
コスト
<Strong>Buffer Pool ヒット率</Strong>で 100 倍以上違う (メモリ ~100ns vs SSD ~100μs)
失敗例
Lock 待ち・デッドロック・disk I/O 飽和はここ
観察方法
SHOW ENGINE INNODB STATUS / Performance Schema (Section 27)

🎮 クエリパイプラインを step-through で追う

SELECT 文がどの段階で何 ms 消費するか、各ステージで何が入力されて何が出力されるかを、1 ステージずつ進めて確認してください。

STAGE 1 / 8
1
Receive
2
Parser
3
Resolver
4
Optimizer
5
Executor
6
Handler
7
InnoDB
8
Send
STAGE 1: Receivetypical latency: 0.1ms
INPUT
TCP パケット
OUTPUT
SQL 文字列
Network スレッドが受信した TCP パケットから SQL 文字列を取り出す。

Volcano モデルと Iterator 実行 (8.0+)

MySQL 8.0 から、Executor は Volcano モデル (Pull-based iterator) で実装し直されました。各実行ノードが Iterator::Read() を実装し、親が子を呼んで 1 行ずつ取り出す設計です。EXPLAIN FORMAT=TREE の出力はこの iterator 木をそのまま表示しています。

-- 例: orders と users を JOIN して集計
EXPLAIN FORMAT=TREE
SELECT u.name, COUNT(*)
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE u.country = 'JP'
GROUP BY u.id;

-> Group aggregate: count(0)               ← Aggregate Iterator
   -> Nested loop inner join                ← NestedLoop Iterator
      -> Filter: (u.country = 'JP')         ← Filter Iterator
         -> Index lookup on u using
            idx_country (country='JP')      ← Index Lookup Iterator (InnoDB)
      -> Index lookup on o using
         idx_user_id (user_id=u.id)         ← Index Lookup Iterator (InnoDB)

# 読み方:
# 1. 一番下から: u (users) を idx_country で 1 行ずつ取り出す
# 2. その u それぞれについて o (orders) を idx_user_id で引く (Nested Loop)
# 3. 上の Filter / Aggregate に流していく
Tip: EXPLAIN FORMAT=TREE は 8.0.16+ から使えます。それ以前の古典的な表形式 EXPLAIN より実際の実行順が読みやすいので、新規プロジェクトではこれをデフォルトに。

Prepared Statement と Statement Cache

同じ SQL のParser / Resolver / Optimizer を毎回走らせるのは無駄です。MySQL は Prepared Statement でこれを再利用できます。

-- 1 回目: Parse + Optimize して実行プランをキャッシュ
PREPARE stmt FROM 'SELECT * FROM users WHERE id = ?';

-- 2 回目以降: 既存プランを再利用 (Parse/Optimize スキップ)
SET @uid = 42;
EXECUTE stmt USING @uid;

SET @uid = 99;
EXECUTE stmt USING @uid;

-- 解放
DEALLOCATE PREPARE stmt;

多くの言語ドライバ (Connector/J ・ mysqli ・ Python の cursor.execute(sql, params) など) は内部で自動的に Prepared Statement を使っています。Web アプリで「同じ SQL を 1 秒に 1,000 回投げる」典型ワークロードでは、これで Parser / Resolver のコストがほぼ消えます。

注意: Server-side prepared statement はセッション単位のキャッシュです。接続をプールしないと意味がなく、接続を毎回張り直すアプリでは Prepared Statement の旨味が出ません。ProxySQL / HikariCP などのプールと組み合わせが前提。
この章のまとめ:① Backend スレッドは 8 段のパイプライン (Receive → Parse → Resolve → Optimize → Execute → Handler → InnoDB → Send)。 ② 性能のほぼ全ては Optimizer + Executor + InnoDB の 3 段で決まる。 ③ EXPLAIN は ④ Optimizer の結果、EXPLAIN ANALYZE は ⑤ Executor の実測。 ④ Prepared Statement で Parse/Resolve のコストを消せる。
SECTION 06

バックグラウンドスレッド

InnoDB の内部には 常時 14 種類前後のバックグラウンドスレッド が動いています。ユーザーの SQL (Backend Thread) とは別に、Buffer Pool の flush・Redo Log の永続化・MVCC の purge・統計情報更新などを裏で支える役割です。

どれが何をしているか分かると、本番でレイテンシが急に悪化したとき「Page Cleaner が追いついていないのか」「Purge が詰まっているのか」を切り分けて対処できます。

14 種類のバックグラウンドスレッド

Master Thread司令塔
1 秒ごとと 10 秒ごとに各タスクを発火させる司令塔。古い設計の名残で、現在は実質的な処理は他スレッドに分散している。
SETTING— (常駐 1 本)
WHEN1s / 10s 周期
Page CleanerI/O
Buffer Pool の dirty page (変更済みでまだディスクに書かれていない) を定期的にディスクへ flush。LRU の末尾を見て古いものから書き出す。
SETTINGinnodb_page_cleaners (4) / innodb_lru_scan_depth
WHEN常時 + Checkpoint
Purge CoordinatorGC
Undo Log の古いバージョンを回収する司令塔。アクティブな最古 Read View より古い undo は捨ててよい (Section 14)。
SETTINGinnodb_purge_threads (4)
WHEN常時
Purge WorkerGC
Coordinator から受け取った undo 回収作業を実行。複数本走る。
SETTINGinnodb_purge_threads
WHEN常時
IO Read ThreadI/O
非同期 read リクエストを処理。Buffer Pool ミスで Page を読む際に使う。
SETTINGinnodb_read_io_threads (4)
WHENBuffer Pool miss
IO Write ThreadI/O
非同期 write リクエストを処理。dirty page の flush を実行。
SETTINGinnodb_write_io_threads (4)
WHENflush 時
IO Log ThreadI/O
Redo Log の flush を担当 (Section 12)。innodb_flush_log_at_trx_commit の設定で挙動が変わる。
SETTING— (常駐 1 本)
WHENCOMMIT / 1s
IO Insert Buffer ThreadI/O
Change Buffer のマージを担当 (Section 13)。Secondary Index への変更をまとめて反映する。
SETTING— (常駐 1 本)
WHEN常時
Log Writer / FlusherI/O
8.0 で新設。Redo Log Buffer → Log File への書き込みを専門化し、Group Commit のスケーラビリティを大幅向上。
SETTINGinnodb_log_writer_threads (ON)
WHEN常時
Log CloserI/O
8.0 で新設。書き終わった Log File ハンドルを閉じる専用スレッド。
SETTING— (常駐 1 本)
WHENlog rotation
Log CheckpointerI/O
8.0 で新設。Sharp Checkpoint (Redo Log を巻き戻し) を発火するタイミングを管理。
SETTING— (常駐 1 本)
WHENCheckpoint 必要時
LRU ManagerGC
Buffer Pool の LRU リストを維持し、古い Clean Page を free list に戻す。
SETTINGinnodb_lru_scan_depth (1024)
WHEN常時
Dict Stats Thread司令塔
テーブル統計情報を非同期に更新。innodb_stats_auto_recalc=ON のとき、テーブル変更率が 10% を超えると発火 (Section 27)。
SETTINGinnodb_stats_auto_recalc (ON)
WHEN変更率 10% 超
Error Monitor / Lock Monitor監視
デッドロック検出・Long lock の警告・SEMAPHORE 待ちの監視を担当。SHOW ENGINE INNODB STATUS の情報源。
SETTINGinnodb_lock_wait_timeout (50)
WHEN常時

カテゴリ別の役割マップ

司令塔
Master Thread / Dict Stats Thread。発火タイミングを決め、他スレッドにタスクを渡す。
I/O
Page Cleaner / IO Read/Write/Log / Log Writer/Flusher/Closer/Checkpointer / Insert Buffer。ディスク I/O を分業して並行化する。
GC
Purge Coordinator / Worker / LRU Manager。古い Undo Log や Clean Page を回収する。
監視
Error Monitor / Lock Monitor。デッドロック検出・Long lock の警告・SHOW ENGINE INNODB STATUS の情報源。

チェックポイントの仕組み — Fuzzy / Sharp Checkpoint

バックグラウンドスレッドの最大の仕事はチェックポイントです。Buffer Pool 上の dirty page を定期的にディスクへ書き戻し、Redo Log の不要部分を捨てる作業 (Section 12 で深掘り)。

Fuzzy Checkpoint (常時)
Page Cleaner が dirty page を少しずつディスクへ書き戻す。サービスを止めない。
innodb_io_capacity で 1 秒あたりの書き出しページ数の上限を制御。
Sharp Checkpoint (緊急)
Redo Log が満杯に近づくと発火。全 dirty page を一気に flush する。
この間、書き込みが詰まりレイテンシが急上昇する。発火させない設計が原則。
Sharp Checkpoint を避ける鉄則: Redo Log を十分大きく取る (innodb_log_file_size を 2GB 〜 4GB)。innodb_io_capacity をストレージ性能 (IOPS) に合わせる (SSD なら 2000〜5000)。詳細は Section 29 で扱います。

バックグラウンドスレッドを観察する

実際に走っているスレッドは Performance Schema から見られます。

-- ① 全スレッド一覧 (Foreground + Background)
SELECT thread_id, name, type, processlist_command, processlist_state
FROM performance_schema.threads
WHERE type = 'BACKGROUND'
ORDER BY name;

-- 出力例:
-- thread/innodb/io_log_thread
-- thread/innodb/io_read_thread
-- thread/innodb/page_cleaner_thread
-- thread/innodb/purge_coordinator_thread
-- thread/innodb/purge_worker_thread (× 3)
-- thread/innodb/log_writer_thread
-- thread/innodb/log_flusher_thread
-- thread/innodb/log_checkpointer_thread
-- thread/innodb/srv_master_thread
-- ...

-- ② SHOW ENGINE INNODB STATUS でも見える
SHOW ENGINE INNODB STATUS\G

-- BACKGROUND THREAD のセクションを見ると
-- srv_master_thread loops: ... 1_second: ... 10_second: ...

-- ③ Buffer Pool の dirty page 比率 (page cleaner の負荷の指標)
SELECT
  POOL_ID,
  DATABASE_PAGES,
  MODIFIED_DATABASE_PAGES,
  ROUND(MODIFIED_DATABASE_PAGES * 100 / DATABASE_PAGES, 2) AS dirty_pct
FROM information_schema.innodb_buffer_pool_stats;

活動タイミングのタイムライン — いつ・何が動くか

各スレッドの発火タイミング を 1 つの時間軸に並べると、「DB が裏で何をしているか」が直感的に分かります。

0s1s2s3s4s5s6s7s8s9s10sMaster Thread1s 毎Master Thread (10s)10s 毎Page Cleaner常時 / 100ms 〜 1sPurge Thread常時IO ReadBuffer Pool miss 時のみIO WritePage Cleaner と連動IO LogCOMMIT 毎 + 1sLog Writer / Flusher常時 (8.0+)Insert BufferMerge 時LRU Manager常時Dict Stats変更率 10% 超時Error / Lock Monitorデッドロック検出時← 時間軸 (10 秒のサンプル) →
読みどころ:Page Cleaner と Log Writer は常に動いている (一番下に近いほど高頻度)。 ② Master Thread は司令塔で 1s / 10s 周期。 ③ I/O 系は発火条件依存 (Buffer miss / COMMIT 等)。 ④ Dict Stats / Lock Monitor は条件成立時にだけ動く。
この章のまとめ:① バックグラウンドスレッドは「ユーザー SQL を支える裏方」。全 14 種類前後。 ② 一番触ることになるのは Page Cleaner (dirty page の flush) と Purge Thread (undo の GC)。 ③ レイテンシ急上昇 → Sharp Checkpoint が発火している可能性が高い。 ④ performance_schema.threadsSHOW ENGINE INNODB STATUS で生きた状態を観察。
SECTION 07

メモリ構造

MySQL のメモリは大きく 「Global Buffer (全接続で共有)」「Session Buffer (接続ごと)」 に分かれます。Global は mysqld 起動時に確保され、Session は接続が来るたびに割り当てられます。

この区別が分かると、本番で OOM Killer に殺された 事故の原因究明が一段速くなります (典型: max_connections × per-thread が物理メモリを超える)。

MySQL のメモリレイアウト

mysqld プロセスメモリGLOBAL BUFFER (全接続で共有・起動時に確保)InnoDB Buffer Poolinnodb_buffer_pool_size (デフォルト 128 MB)本番: 物理メモリの 50〜75% を割当て16KB Page × N・データ Page (テーブル本体)・Index Page (B+Tree)・Undo Log Page・Hash Index / Lock InfoRedo Log Buffer16 MBChange Buffer内 25%Adaptive Hash Index自動Dictionary Cache+SESSION BUFFER (接続ごと・必要なときに割当て)× max_connections 分だけ最大確保されるsort_buffer_size256 KBORDER BY のソート用join_buffer_size256 KBBlock Nested Loop Join 用read_buffer_size128 KBsequential read 用read_rnd_buffer_size256 KBrandom read 用tmp_table_size16 MB (上限)MEMORY 一時テーブルbinlog_cache_size32 KBbinlog バッファ (TX 単位)thread_stack1 MBスレッドのスタックnet_buffer_length16 KBクライアント通信 buffer

Global バッファの中身

InnoDB Buffer Poolinnodb_buffer_pool_size
デフォルト 128 MB / 本番は物理 50〜75%
MySQL のすべてのパフォーマンスはここで決まる最重要バッファ。テーブルデータ・インデックス・Undo Log のすべての 16 KB Page をキャッシュする。Buffer Pool ヒット率を 99% 以上に保つのが本番の基本。
TUNINGSection 29 で詳説
Redo Log Bufferinnodb_log_buffer_size
デフォルト 16 MB
コミット前の Redo Log を一時的に蓄えるバッファ。COMMIT 時にディスクの ib_logfile に flush される (Section 12)。
TUNING巨大トランザクションを扱うときだけ増やす
Change Bufferinnodb_change_buffer_max_size (%)
Buffer Pool の 25%
Secondary Index への変更をまとめてバッチ反映するための領域 (Section 13)。書き込み多めのワークロードで効く。
TUNING読み多めなら 5〜10% に下げる
Adaptive Hash Indexinnodb_adaptive_hash_index
8.0 デフォルト OFF (5.x は ON)
よく検索される B+Tree ノードを自動で Hash 化してくれる仕組み (Section 13)。8.0 で並行性問題が露呈し、デフォルト OFF に。
TUNING基本 OFF のまま
Dictionary Cacheinnodb_dict_size
自動管理
テーブル定義 (列・index・FK) をキャッシュ。SHOW TABLES や DESC の高速化。
TUNING通常触らない

Session バッファの計算式

Session バッファは 接続が来たときに必要に応じて割り当てられる 性質があります (常に max まで使うわけではない)。とはいえ 最悪ケース を見積もるのが本番設計の鉄則です。

# 1 接続あたりの最大メモリ消費 (典型)
sort_buffer_size       256 KB
+ join_buffer_size     256 KB
+ read_buffer_size     128 KB
+ read_rnd_buffer_size 256 KB
+ tmp_table_size       16 MB    ← MEMORY 一時テーブルの上限
+ binlog_cache_size    32 KB
+ thread_stack         1 MB
+ net_buffer_length    16 KB
─────────────────────────────
~ 17.9 MB / 接続 (最悪ケース)

# max_connections=500 なら最悪
500 × 17.9 MB = ~9 GB

# Global と合わせると
12 GB (Buffer Pool) + 9 GB (Session 最悪) + α = 物理 24 GB 程度欲しい
OOM 事故の典型パターン: sort_buffer_size を「大きくすれば速くなる」と信じて 16 MB などに上げる → max_connections=1000 のサーバで 16 GB × 1000 = 16 TB をいきなり確保できないので Session バッファは クエリ単位 で確保される、と頭で分かっていても、ピーク時に並行クエリが集中すると本当に物理メモリが枯渇して OOM Killer に mysqld が殺される。
sort_buffer_size はデフォルト維持 (256 KB)。大きくしないと駄目なクエリは個別に SET SESSION sort_buffer_size = ... で増やすのが鉄則。

メモリ使用量を観察する

実際にどれだけメモリを使っているかは Performance Schema で見られます。

-- ① mysqld 全体のメモリ使用
SELECT
  SUM(CURRENT_NUMBER_OF_BYTES_USED) / 1024 / 1024 AS used_MB
FROM performance_schema.memory_summary_global_by_event_name;

-- ② 種類別のメモリ消費 TOP 10
SELECT
  event_name,
  ROUND(CURRENT_NUMBER_OF_BYTES_USED / 1024 / 1024, 2) AS used_MB,
  CURRENT_COUNT_USED AS alloc_count
FROM performance_schema.memory_summary_global_by_event_name
ORDER BY CURRENT_NUMBER_OF_BYTES_USED DESC
LIMIT 10;

-- ③ Buffer Pool ヒット率 (最重要メトリック)
SELECT
  POOL_ID,
  DATABASE_PAGES,
  ROUND((1 - HIT_RATE / 1000) * 100, 4) AS miss_pct
FROM information_schema.innodb_buffer_pool_stats;

-- HIT_RATE は 1000 = 100% ヒット
-- 999 以上が望ましい (= ミス率 0.1% 未満)

Buffer Pool の動的 resize — innodb_buffer_pool_chunk_size

5.7+ から Buffer Pool を稼働中に変更できる ようになりました。仕組みは「Buffer Pool を innodb_buffer_pool_chunk_size (デフォルト 128 MB) のチャンク単位 で確保し、resize ではチャンク数を増減する」というもの。

-- ① 現状の Buffer Pool サイズと chunk 設定
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';        -- 12G
SHOW VARIABLES LIKE 'innodb_buffer_pool_chunk_size';  -- 128M
SHOW VARIABLES LIKE 'innodb_buffer_pool_instances';   -- 8

-- 内部式: size = chunk_size × instances × N
--   12G = 128M × 8 × 12

-- ② 動的に拡張 (mysqld 再起動なし)
SET GLOBAL innodb_buffer_pool_size = 16 * 1024 * 1024 * 1024;  -- 16G

-- ③ Resize の進行状況を見る
SHOW STATUS LIKE 'Innodb_buffer_pool_resize_status';
-- "Resizing buffer pool from 12884901888 to 17179869184 (units: 134217728)."
-- "Withdrawing blocks: ..."
-- "Resizing also other hash tables..."
-- "Completed resizing buffer pool at ..."

-- ④ サイズ単位を変えたいなら chunk_size を再設定
-- ※ chunk_size は再起動が必要・小さくすると粒度が細かくなる
resize の注意点:必ず chunk_size × instances の倍数 に丸められる。 ② 縮小は dirty page の flush を伴うため時間がかかる。 ③ resize 中はクエリが若干遅くなる (latch 競合) — オフピーク時にやる。 ④ Aurora MySQL や Cloud SQL ではマネージドが自動管理するので手動 resize は基本不可。
この章のまとめ:① Global バッファは起動時に確保 (Buffer Pool が支配的)。 ② Session バッファは接続ごとに確保 (sort_buffer / join_buffer / tmp_table が支配的)。 ③ メモリ式: 物理 RAM ≥ Buffer Pool + max_connections × per-thread + OS。 ④ sort_buffer_size の無闇な拡大は OOM の典型原因。 ⑤ Buffer Pool は chunk_size × instances の倍数 で動的 resize 可能。
SECTION 08

設定ファイル

MySQL の挙動はすべて my.cnf (Windows では my.ini) で制御します。本章では my.cnf の構造・読み込み順序・SET PERSIST による動的永続化までを扱います。8.0 で「再起動が必要な設定変更が大幅に減った」のは運用上の大きな変化です。

my.cnf の読み込み順序

mysqld は起動時に複数の設定ファイルを順番に読み込み後勝ちのルールで上書きします。同じパラメータが複数箇所にあると最後に読まれたものが採用されます。

# Linux での読み込み順 (後勝ち)
1. /etc/my.cnf
2. /etc/mysql/my.cnf
3. SYSCONFDIR/my.cnf
4. $MYSQL_HOME/my.cnf
5. defaults-extra-file (--defaults-extra-file=...)
6. ~/.my.cnf
7. ~/.mylogin.cnf

# 確認方法
mysql --help | grep -A1 "Default options"
mysqld --verbose --help | grep -A1 "Default options"

# include で別ファイルを取り込む
!includedir /etc/mysql/conf.d/        # ディレクトリ内の *.cnf を全部
!include /etc/mysql/extra.cnf           # 単一ファイル
運用 Tip: 本番では /etc/mysql/my.cnf + !includedir /etc/mysql/conf.d/ を使い、機能ごとに別ファイル (innodb.cnf / replication.cnf / charset.cnf 等) に分けると、変更の差分管理がやりやすくなります。

my.cnf のセクション

my.cnf は [mysqld][client] など、コマンドごとにセクションが分かれています。それぞれそのコマンドが起動時に読みます。

# /etc/mysql/my.cnf — 典型例

[mysqld]
# mysqld (サーバ) のオプション
datadir          = /var/lib/mysql
port             = 3306
socket           = /var/run/mysqld/mysqld.sock
default-storage-engine = InnoDB

# 文字コード
character-set-server = utf8mb4
collation-server     = utf8mb4_0900_ai_ci

# InnoDB チューニング
innodb_buffer_pool_size = 12G
innodb_log_file_size    = 2G
innodb_flush_log_at_trx_commit = 1
innodb_io_capacity      = 2000

# 接続制限
max_connections     = 500
wait_timeout        = 28800

# ログ
slow_query_log     = ON
slow_query_log_file = /var/log/mysql/slow.log
long_query_time    = 1.0

# レプリケーション
server-id          = 1
log-bin            = /var/log/mysql/binlog
binlog_format      = ROW
gtid_mode          = ON
enforce_gtid_consistency = ON

[client]
# mysql / mysqldump 共通
port    = 3306
socket  = /var/run/mysqld/mysqld.sock
default-character-set = utf8mb4

[mysql]
# mysql CLI 専用
prompt = "\u@\h [\d]> "
auto-rehash

[mysqldump]
quick
single-transaction
default-character-set = utf8mb4

SHOW VARIABLES / SET PERSIST — 動的変更

8.0 から SET PERSIST / SET PERSIST_ONLY が追加され、my.cnf を編集せずに永続化できる設定変更が可能になりました。これが運用上は革命でした。

-- ① 現在の設定値を見る
SHOW VARIABLES LIKE 'innodb_%';
SHOW GLOBAL VARIABLES LIKE 'max_connections';

-- ② 一時的に変える (再起動で消える)
SET GLOBAL max_connections = 1000;

-- ③ 8.0+ : 永続化される (mysqld-auto.cnf に保存)
SET PERSIST max_connections = 1000;

-- ④ 動的変更不可な変数だけ永続化したい
SET PERSIST_ONLY innodb_buffer_pool_size = '12G';
-- ↑ 即時反映はせず、次回起動時から適用

-- ⑤ 永続化された設定を確認
SELECT * FROM performance_schema.persisted_variables;

-- ⑥ 永続化を取り消す
RESET PERSIST max_connections;
RESET PERSIST;  -- 全部リセット

-- ⑦ セッション単位の変更 (自分の接続のみ)
SET SESSION sort_buffer_size = 1048576;  -- 1 MB
動的変更可 (即時反映)
max_connections / slow_query_log / long_query_time / innodb_io_capacity / sync_binlog ほか多数
再起動が必要
innodb_log_file_size / innodb_log_files_in_group / port / datadir / character-set-server / server-id ほか
運用フロー:① 即時試したい → SET GLOBAL で動的変更 → ② 結果が良ければ SET PERSIST で永続化 → ③ Infrastructure as Code に my.cnf テンプレートを反映 → ④ RESET PERSIST で mysqld-auto.cnf をクリーンに

設定値を見るときの落とし穴

設定回りで最もハマるのが「反映されたつもりが反映されていない」事故です。チェックポイントを整理します。

my.cnf を編集したが反映されない
mysqld を再起動していない。systemctl restart mysql で再起動するか、動的変更可なら SET GLOBAL で即時適用。
SET GLOBAL したが値が違う
別の my.cnf (~/.my.cnf や conf.d) で上書きされている。SHOW VARIABLES の結果と my.cnf に書いた値が違ったら、読み込み順を疑う。
SET PERSIST が効かない
mysqld-auto.cnf の場所がデータディレクトリ内 (datadir/mysqld-auto.cnf)。これが書き込み権限不足 / 読み込み権限不足だと黙って効かない。
PERSIST_ONLY なのに即時反映されると思っていた
SET PERSIST_ONLY次回起動時から適用。今すぐ反映したいなら通常の SET PERSIST
SHOW VARIABLES と実際の挙動が違う
SHOW VARIABLES はセッション値を見ている (GLOBAL を見たいなら SHOW GLOBAL VARIABLES)。InnoDB の一部設定はセッション単位で上書き不可 (例: innodb_buffer_pool_size)。
この章のまとめ:① my.cnf は読み込み順 + セクション ([mysqld] 等) で構成。 ② !includedir で機能別に分割管理がオススメ。 ③ 8.0 の SET PERSIST は強力 — my.cnf 編集なしで永続化可能。 ④ 「効かない」と感じたら読み込み順動的変更可否を疑う。
─ PREMIUM ─

ここから先はプレミアム会員限定

テーブル設計・InnoDB 内部・MVCC・Redo Log・インデックス・実行計画・チューニング・レプリケーション・バックアップ・セキュリティまで、MySQL の中身を本気で理解するための全要素はプレミアムプランで読めます。

プレミアムを見る

関連コンテンツ

MySQL 完全攻略 | Vizigo