T-CREATOR

MySQL EXPLAIN/EXPLAIN ANALYZE 速読チートシート:各列の意味と対処法

MySQL EXPLAIN/EXPLAIN ANALYZE 速読チートシート:各列の意味と対処法

データベースのパフォーマンス改善に取り組む際、最初に確認すべきなのがクエリの実行計画です。MySQL ではEXPLAINEXPLAIN ANALYZEというコマンドを使って、クエリがどのように実行されるかを詳しく分析できます。

しかし、EXPLAIN の出力結果には多くの列があり、初めて見る方は「どこから見ればいいの?」「この値は問題があるの?」と戸惑うことも多いでしょう。本記事では、EXPLAIN/EXPLAIN ANALYZE の各列の意味と、問題発見時の具体的な対処法を速読できる形式でまとめました。

チートシートとして活用できるよう、実務でよく遭遇するパターンと解決策を中心に解説していきますね。

EXPLAIN 速読早見表

本記事の内容を素早く参照できるよう、重要な情報を表形式でまとめました。パフォーマンス問題の診断時にご活用ください。

速読 3 ステップチェックリスト

EXPLAIN の結果を効率的に読み解くための優先順位です。

ステップ確認項目チェックポイント問題の兆候即座の対処
1type 列テーブルアクセス方法ALL, index が表示インデックス追加を検討
2rows 列処理される推定行数10000 行以上インデックス最適化または分割
3Extra 列追加の実行情報Using filesort, Using temporaryインデックス追加またはクエリ修正
-key 列使用されているインデックスNULL(インデックス未使用)適切なインデックスを作成

type 列:アクセス方法評価早見表

type列はパフォーマンスの最重要指標です。下に行くほどパフォーマンスが悪化します。

type 値評価意味処理速度対処の必要性主な対処法
system★★★テーブルに 1 行のみ最速不要-
const★★★PRIMARY KEY/UNIQUE での等値検索最速不要-
eq_ref★★★結合時に 1 行のみマッチ高速不要-
ref★★☆非ユニークインデックスでの検索良好通常不要rows が大きい場合は複合インデックス
fulltext★★☆FULLTEXT インデックス使用良好通常不要-
ref_or_null★★☆ref + NULL 値検索良好通常不要-
index_merge★☆☆複数インデックスのマージ注意要確認複合インデックスに統合
range★☆☆範囲検索(BETWEEN, >, < など)許容範囲要確認rows が大きい場合は条件を狭める
index★☆☆インデックスフルスキャン要改善必要WHERE 句追加または LIMIT 句
ALL☆☆☆テーブルフルスキャン危険即座に必要インデックス追加必須

Extra 列:追加情報評価早見表

Extra列には実行方法の詳細が表示されます。複数の値が同時に表示されることもあります。

Extra 値評価意味対処の必要性主な対処法
Using index★★★カバリングインデックス使用不要(最良)-
Using where★★☆WHERE 句でフィルタリング通常不要type が ALL なら要改善
Using index condition★★☆Index Condition Pushdown不要(良好)-
Using filesort★☆☆ソート処理が発生必要ソート列にインデックス追加
Using temporary★☆☆一時テーブル使用必要GROUP BY 列にインデックス、クエリ書換え
Using join buffer★☆☆結合バッファ使用要確認結合キーにインデックス追加
Impossible WHERE☆☆☆WHERE 条件が常に false即座に必要クエリロジックを修正
No matching row☆☆☆該当行なし要確認データ確認またはクエリ条件見直し

rows 値:処理行数判断早見表

rows列の値は、テーブルの総行数に対する割合で評価することが重要です。

rows の値評価状態対処の必要性主な対処法
1〜100★★★良好不要-
101〜1000★★☆注意が必要要確認インデックスの見直し
1001〜10000★☆☆要改善必要複合インデックス、条件の絞り込み
10001〜☆☆☆即改善必須即座に必要インデックス追加、クエリ分割、統計更新

※ テーブルの総行数が 100 万行以上の場合、基準値は 10 倍程度に調整してください。

問題パターンと対処法早見表

よく遭遇する問題パターンと、その対処法を優先順位順にまとめました。

問題パターン症状(EXPLAIN 結果)優先度推奨対処法効果
テーブルフルスキャンtype: ALLWHERE 句の列にインデックス追加★★★
インデックスフルスキャンtype: indexWHERE 句追加、LIMIT 句で制限★★☆
ソート処理の発生Extra: Using filesortORDER BY 列にインデックス追加★★★
一時テーブル使用Extra: Using temporaryGROUP BY を使用、EXISTS に書換え★★☆
大量行の処理rows: 10000 以上複合インデックス、統計情報更新★★★
後方一致・部分一致 LIKEtype: ALL + WHERE LIKE '%...'FULLTEXT インデックス、検索条件の変更★★★
WHERE 句で関数使用type: ALL + WHERE FUNCTION(col) = ...関数を使わない形に書換え★★★
IN サブクエリExtra: Materialize subqueryEXISTS または JOIN に書換え★★☆
複数インデックスのマージtype: index_merge複合インデックスに統合★★☆
カバリングインデックス未使用key は使用されるが Extra に Using index なしSELECT 句を必要な列のみに限定★☆☆
統計情報の乖離rows と actual rows が 10 倍以上乖離ANALYZE TABLE で統計情報更新★★☆

EXPLAIN ANALYZE 実測値の確認ポイント

EXPLAIN ANALYZEで取得できる実測値の確認ポイントです。

項目説明確認内容問題の兆候
actual time実際の実行時間(ms)開始時間..終了時間100ms 以上は要改善
actual rows実際に処理された行数推定 rows との比較推定と 10 倍以上の乖離は要注意
loops処理のループ回数ネストループ結合の回数1 以外は結合方法を要確認
cost推定コスト相対的な処理コストの比較他のクエリとの比較に使用

この早見表を参照しながら、以降の詳細解説をお読みいただくと、より理解が深まります。

背景

MySQL のクエリ実行プロセス

MySQL が SQL クエリを受け取ると、内部で複数のステップを経て結果を返します。

mermaidflowchart TD
  parse["1. SQL解析<br/>(Parser)"] --> optimize["2. 最適化<br/>(Optimizer)"]
  optimize --> plan["3. 実行計画生成<br/>(Execution Plan)"]
  plan --> execute["4. 実行<br/>(Executor)"]
  execute --> result["5. 結果返却"]

  style plan fill:#e1f5ff

上記の図が示すように、実行計画の生成は最適化フェーズで行われます。この実行計画こそが EXPLAIN で確認できる内容なのです。

EXPLAIN と EXPLAIN ANALYZE の違い

MySQL には 2 種類の実行計画確認方法があります。

#コマンド説明実行有無取得できる情報
1EXPLAIN実行計画のみ表示実行しない予測値(推定行数など)
2EXPLAIN ANALYZE実行計画+実測値実際に実行実測値(実行時間、実際の行数)

EXPLAINは実際にクエリを実行せず、オプティマイザの予測だけを表示するため安全ですが、予測が外れていることもあります。一方、EXPLAIN ANALYZEは実際にクエリを実行して実測値を取得するため、より正確ですが本番環境では注意が必要です。

パフォーマンス問題の早期発見が重要な理由

データベースのパフォーマンス問題は、アプリケーション全体のユーザー体験に直結します。

レスポンスタイムが 1 秒増えるごとに、ユーザーの離脱率が大きく上昇するという調査結果もあるんです。特に Web アプリケーションでは、データベースのクエリ実行時間がボトルネックになりやすいため、開発段階から EXPLAIN を活用した分析が欠かせません。

課題

EXPLAIN 出力の複雑さ

EXPLAIN を実行すると、多数の列が表示されます。

sqlEXPLAIN SELECT * FROM users WHERE email = 'test@example.com';

このシンプルなクエリでも、以下のような多くの列が出力されます。

列名情報の種類
idクエリ内の実行順序
select_typeSELECT 文の種類
table対象テーブル名
partitions使用されるパーティション
type結合タイプ(重要度 ★★★)
possible_keys使用可能なインデックス候補
key実際に使用されるインデックス
key_lenインデックスキーの長さ
refインデックス検索の参照列
rows処理される推定行数(重要度 ★★★)
filteredフィルタリング後の推定割合
Extra追加情報(重要度 ★★★)

どの列を優先的に確認すべきか、どの値が問題なのかを判断するのは、初心者にとって大きなハードルとなっています。

問題パターンの見逃し

実行計画を見ても、「これが良いのか悪いのか」が分からないケースが多々あります。

mermaidflowchart LR
  explain["EXPLAIN実行"] --> check["結果を確認"]
  check --> question["?<br/>これは問題?"]
  question -->|判断できない| skip["スルー"]
  skip --> performance["パフォーマンス<br/>問題発生"]

  style question fill:#ffe0e0
  style performance fill:#ff8080

典型的な問題パターンを知らないと、深刻なパフォーマンス問題を見逃してしまう危険性があるんですね。

対処法の選択肢が多すぎる問題

パフォーマンス問題を発見しても、解決方法は多岐にわたります。

インデックスを追加すべきか、クエリを書き直すべきか、テーブル設計を見直すべきか——選択肢が多すぎて、どれから手をつけるべきか迷ってしまいます。各問題パターンに対する効果的な対処法を体系的に把握する必要があるでしょう。

解決策

EXPLAIN 速読の 3 ステップアプローチ

効率的に EXPLAIN を読み解くには、優先順位をつけたアプローチが有効です。

mermaidflowchart TD
  start["EXPLAIN実行"] --> step1["ステップ1:<br/>typeをチェック"]
  step1 --> step2["ステップ2:<br/>rowsをチェック"]
  step2 --> step3["ステップ3:<br/>Extraをチェック"]
  step3 --> decision{"問題発見?"}
  decision -->|Yes| fix["対処実施"]
  decision -->|No| ok["OK"]

  style step1 fill:#fff4e0
  style step2 fill:#fff4e0
  style step3 fill:#fff4e0

このアプローチに従えば、重要な問題を見逃すことなく、効率的に分析できます。

ステップ 1:type 列の確認と評価

type列は、テーブルへのアクセス方法を示す最重要指標です。

type 列の値とパフォーマンス評価

#type 値評価意味パフォーマンス
1system★★★テーブルに 1 行のみ最速
2const★★★PRIMARY KEY または UNIQUE 索引での検索最速
3eq_ref★★★結合時に 1 行のみマッチ高速
4ref★★☆非ユニーク索引での検索良好
5fulltext★★☆FULLTEXT 索引使用良好
6ref_or_null★★☆ref + NULL 値検索良好
7index_merge★☆☆複数インデックスのマージ注意
8range★☆☆範囲検索許容範囲
9index★☆☆インデックスフルスキャン要改善
10ALL☆☆☆テーブルフルスキャン危険

問題のある type 値とその対処法

ALL が表示された場合

sql-- 問題のあるクエリ例
EXPLAIN SELECT * FROM orders
WHERE customer_name = 'Yamada';

このクエリでtype: ALLが表示された場合の対処法です。

sql-- 対処法1:適切なインデックスを追加
CREATE INDEX idx_customer_name
ON orders(customer_name);

インデックスを追加することで、typerefに改善されます。

sql-- 対処法2:WHERE句の条件を見直す
-- 関数を使うとインデックスが効かない
-- NG例
WHERE YEAR(order_date) = 2024

-- OK例
WHERE order_date >= '2024-01-01'
  AND order_date < '2025-01-01'

関数を使わない形に書き換えることで、インデックスが有効になるんですね。

index が表示された場合

sql-- インデックスフルスキャンが発生する例
EXPLAIN SELECT id FROM orders;

type: indexは、インデックス全体をスキャンしている状態です。

sql-- 対処法1:必要な行だけを取得するようWHERE句を追加
SELECT id FROM orders
WHERE status = 'completed';

-- 対処法2:LIMIT句で取得行数を制限
SELECT id FROM orders
ORDER BY created_at DESC
LIMIT 100;

取得する行を絞り込むことで、パフォーマンスが大幅に改善されます。

ステップ 2:rows 列の確認と評価

rows列は、MySQL が処理すると予測している行数を示します。

rows 値の判断基準

mermaidflowchart TD
  check["rowsの値を確認"] --> small{"100行以下?"}
  small -->|Yes| good["良好"]
  small -->|No| medium{"1000行以下?"}
  medium -->|Yes| caution["注意が必要"]
  medium -->|No| large{"10000行以下?"}
  large -->|Yes| warning["要改善"]
  large -->|No| critical["即改善必須"]

  style good fill:#d4edda
  style caution fill:#fff3cd
  style warning fill:#f8d7da
  style critical fill:#ff8080

ただし、テーブルの総行数によって評価は変わります。100 万行のテーブルで 1000 行なら優秀ですが、100 行のテーブルで 1000 行なら明らかに異常です。

rows 値が大きい場合の対処法

対処法 1:インデックスの追加または最適化

sql-- 現状確認
EXPLAIN SELECT * FROM products
WHERE category = 'electronics'
  AND price > 10000;
-- rows: 50000 と表示された場合

複合インデックスを作成します。

sql-- 複合インデックスの作成
CREATE INDEX idx_category_price
ON products(category, price);

インデックスの列順序は、等値条件(=)を先に、範囲条件(>, <)を後にするのが基本です。

対処法 2:統計情報の更新

sql-- テーブルの統計情報を更新
ANALYZE TABLE products;

統計情報が古いと、オプティマイザの推定が不正確になります。定期的な更新が重要ですね。

対処法 3:クエリの分割

sql-- 大量の行を処理するクエリ
-- NG例
UPDATE orders SET status = 'archived'
WHERE created_at < '2020-01-01';
-- rows: 500000

大量更新は、バッチ処理に分割します。

sql-- OK例:バッチ処理に分割
-- 1回あたり1000件ずつ処理
UPDATE orders SET status = 'archived'
WHERE created_at < '2020-01-01'
  AND status != 'archived'
LIMIT 1000;

これを繰り返し実行することで、ロックの影響を最小限にできます。

ステップ 3:Extra 列の確認と対処

Extra列には、実行計画に関する追加情報が表示されます。

頻出する Extra 値と評価

#Extra 値評価意味対処必要性
1Using index★★★カバリングインデックス使用最良
2Using where★★☆WHERE 句でフィルタリング通常
3Using index condition★★☆Index Condition Pushdown良好
4Using filesort★☆☆ファイルソート発生要改善
5Using temporary★☆☆一時テーブル使用要改善
6Using join buffer★☆☆結合バッファ使用要確認
7Impossible WHERE☆☆☆WHERE 条件が矛盾要修正
8No matching row☆☆☆該当行なし要確認

Using filesort への対処

Using filesortは、ソート処理がメモリまたはディスク上で実行されることを示します。

sql-- Using filesortが発生する例
EXPLAIN SELECT * FROM users
ORDER BY last_login_at DESC
LIMIT 10;

ソート対象の列にインデックスを作成します。

sql-- 対処法:ソート列にインデックスを追加
CREATE INDEX idx_last_login
ON users(last_login_at DESC);

降順ソートの場合は、DESCを指定したインデックスが効果的です。

sql-- 複数列でソートする場合
EXPLAIN SELECT * FROM users
ORDER BY status, last_login_at DESC;

複合インデックスを作成します。

sql-- ソート順序に合わせた複合インデックス
CREATE INDEX idx_status_login
ON users(status ASC, last_login_at DESC);

ソート順序を含めたインデックス定義により、パフォーマンスが劇的に向上します。

Using temporary への対処

Using temporaryは、結果を一時テーブルに格納していることを示します。

sql-- Using temporaryが発生する例
EXPLAIN SELECT DISTINCT category
FROM products
WHERE price > 1000;

対処法を見ていきましょう。

sql-- 対処法1:GROUP BYに書き換え
SELECT category
FROM products
WHERE price > 1000
GROUP BY category;
sql-- 対処法2:サブクエリの最適化
-- NG例
SELECT u.* FROM users u
WHERE u.id IN (
  SELECT DISTINCT user_id FROM orders
);

EXISTS に書き換えます。

sql-- OK例
SELECT u.* FROM users u
WHERE EXISTS (
  SELECT 1 FROM orders o
  WHERE o.user_id = u.id
);

EXISTSは、最初にマッチした時点で評価を終了するため、より効率的なんです。

EXPLAIN ANALYZE で実測値を確認

EXPLAIN ANALYZEを使うと、予測と実測の乖離を確認できます。

sql-- EXPLAIN ANALYZEの実行例
EXPLAIN ANALYZE
SELECT * FROM orders
WHERE status = 'pending'
  AND created_at > '2024-01-01';

出力例の見方を解説します。

text-> Filter: ((orders.status = 'pending') and (...))
   (cost=250.00 rows=100) (actual time=0.05..2.5 rows=85 loops=1)
   -> Table scan on orders
      (cost=250.00 rows=1000) (actual time=0.03..2.3 rows=1000 loops=1)

重要な指標の読み方

項目説明確認ポイント
cost推定コスト相対的な比較に使用
rows推定行数actual rows と比較
actual time実際の実行時間(ms)開始..終了の形式
actual rows実際の処理行数rows との乖離を確認
loopsループ回数1 以外なら要注意

推定行数と実際の行数に大きな乖離がある場合は、統計情報の更新が必要です。

sql-- 統計情報を更新
ANALYZE TABLE orders;

具体例

例 1:テーブルフルスキャンの改善

実務でよく遭遇する問題を見ていきましょう。

問題のあるクエリ

sql-- ユーザー検索のクエリ
SELECT * FROM users
WHERE email LIKE '%@example.com';

EXPLAIN を実行します。

sqlEXPLAIN SELECT * FROM users
WHERE email LIKE '%@example.com';

EXPLAIN 結果の分析

項目評価
typeALL☆☆☆(危険)
rows100000全行スキャン
ExtraUsing whereフィルタリングのみ

前方一致でないLIKE検索では、インデックスが使用されません。

対処法の実装

sql-- 対処法1:前方一致に変更できる場合
SELECT * FROM users
WHERE email LIKE 'user@%';

ビジネス要件が許せば、検索条件を変更するのが最も効果的です。

sql-- 対処法2:全文検索インデックスを使用
ALTER TABLE users
ADD FULLTEXT INDEX ft_email(email);
sql-- 全文検索での検索
SELECT * FROM users
WHERE MATCH(email) AGAINST('example.com' IN BOOLEAN MODE);

全文検索インデックスにより、部分一致検索も高速化できるんですね。

例 2:結合クエリの最適化

複数テーブルの結合は、パフォーマンス問題の温床です。

問題のあるクエリ

sql-- 注文情報とユーザー情報を結合
SELECT o.*, u.name, u.email
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.status = 'pending';

EXPLAIN で確認します。

sqlEXPLAIN SELECT o.*, u.name, u.email
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.status = 'pending';

EXPLAIN 結果の問題点

mermaidflowchart LR
  orders["ordersテーブル<br/>(type: ALL)<br/>rows: 50000"] --> join["結合処理"]
  users["usersテーブル<br/>(type: eq_ref)<br/>rows: 1"] --> join
  join --> result["結果"]

  style orders fill:#ff8080
  style users fill:#d4edda

ordersテーブルがフルスキャンになっているのが問題です。

段階的な改善

ステップ 1:WHERE 句の列にインデックス追加

sqlCREATE INDEX idx_status
ON orders(status);
sql-- 改善後のEXPLAIN確認
EXPLAIN SELECT o.*, u.name, u.email
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.status = 'pending';

改善後の結果です。

テーブルtyperows改善度
ordersref500100 倍改善
userseq_ref1良好

ステップ 2:カバリングインデックスの活用

sql-- よく使う列を含む複合インデックス
CREATE INDEX idx_status_user
ON orders(status, user_id, created_at);
sql-- 必要な列だけを取得するようクエリ修正
SELECT o.created_at, o.total_amount,
       u.name, u.email
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.status = 'pending';

カバリングインデックスにより、Extra: Using indexが表示され、テーブルへのアクセスが不要になります。

例 3:サブクエリの最適化

サブクエリは便利ですが、パフォーマンス問題の原因になりやすいです。

問題のあるクエリ

sql-- 未払いの注文があるユーザーを取得
SELECT * FROM users
WHERE id IN (
  SELECT user_id FROM orders
  WHERE status = 'unpaid'
);

EXPLAIN ANALYZE で実測値を確認します。

sqlEXPLAIN ANALYZE
SELECT * FROM users
WHERE id IN (
  SELECT user_id FROM orders
  WHERE status = 'unpaid'
);

問題の特定

text-> Filter: (users.id in (...))
   (cost=1250.00 rows=500) (actual time=0.5..150.2 rows=450 loops=1)
   -> Table scan on users
      (cost=250.00 rows=5000) (actual time=0.02..1.5 rows=5000 loops=1)
   -> Materialize subquery
      (cost=1000.00 rows=1000) (actual time=148.5..148.5 rows=1000 loops=1)

サブクエリが一時テーブルにマテリアライズされ、時間がかかっています。

対処法の実装

sql-- JOINに書き換え
SELECT DISTINCT u.*
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE o.status = 'unpaid';

さらに改善します。

sql-- EXISTSを使った書き換え(重複排除不要)
SELECT u.*
FROM users u
WHERE EXISTS (
  SELECT 1 FROM orders o
  WHERE o.user_id = u.id
    AND o.status = 'unpaid'
);

改善結果の確認

sqlEXPLAIN ANALYZE
SELECT u.*
FROM users u
WHERE EXISTS (
  SELECT 1 FROM orders o
  WHERE o.user_id = u.id
    AND o.status = 'unpaid'
);

改善後のパフォーマンス比較です。

方式実行時間改善率
IN (サブクエリ)150.2ms-
INNER JOIN45.3ms70%改善
EXISTS25.1ms83%改善

EXISTSが最も効率的な結果となりました。

例 4:GROUP BY と ORDER BY の最適化

集計クエリもパフォーマンスに影響します。

問題のあるクエリ

sql-- カテゴリ別の売上集計
SELECT category, COUNT(*) as cnt, SUM(price) as total
FROM products
WHERE status = 'active'
GROUP BY category
ORDER BY total DESC;

EXPLAIN で確認しましょう。

sqlEXPLAIN SELECT category, COUNT(*) as cnt, SUM(price) as total
FROM products
WHERE status = 'active'
GROUP BY category
ORDER BY total DESC;

EXPLAIN 結果の問題点

項目問題点
typeALLフルスキャン
ExtraUsing where; Using temporary; Using filesort3 重苦

一時テーブルとファイルソートの両方が発生しています。

対処法の実装

ステップ 1:WHERE 句と GROUP BY 用の複合インデックス

sqlCREATE INDEX idx_status_category
ON products(status, category);

これにより、type: rangeに改善され、Using temporaryが解消されます。

ステップ 2:ソートの最適化

sql-- 計算列でのソートは避けられないため
-- インデックスは効かない
-- 代替案:必要な行数だけ取得
SELECT category, COUNT(*) as cnt, SUM(price) as total
FROM products
WHERE status = 'active'
GROUP BY category
ORDER BY total DESC
LIMIT 20;

LIMITを追加することで、ソート対象を絞り込めます。

ステップ 3:サマリーテーブルの活用

sql-- 集計結果を保存するテーブル
CREATE TABLE category_summary (
  category VARCHAR(100) PRIMARY KEY,
  product_count INT,
  total_price DECIMAL(10,2),
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
sql-- 定期的に集計結果を更新
INSERT INTO category_summary
  (category, product_count, total_price)
SELECT category, COUNT(*), SUM(price)
FROM products
WHERE status = 'active'
GROUP BY category
ON DUPLICATE KEY UPDATE
  product_count = VALUES(product_count),
  total_price = VALUES(total_price),
  updated_at = CURRENT_TIMESTAMP;

リアルタイム性が不要なら、事前集計が最も効果的なんですね。

まとめ

EXPLAIN/EXPLAIN ANALYZE は、MySQL のパフォーマンスチューニングに欠かせないツールです。本記事で紹介した速読アプローチをまとめます。

速読 3 ステップの振り返り

mermaidflowchart TD
  start["EXPLAIN実行"] --> check1{"type列確認"}
  check1 -->|ALL/index| fix1["インデックス追加<br/>クエリ修正"]
  check1 -->|ref以上| check2{"rows列確認"}

  check2 -->|大きい| fix2["統計更新<br/>インデックス最適化"]
  check2 -->|小さい| check3{"Extra列確認"}

  check3 -->|filesort/temporary| fix3["インデックス追加<br/>クエリ書き換え"]
  check3 -->|問題なし| done["完了"]

  fix1 --> verify["EXPLAIN再実行"]
  fix2 --> verify
  fix3 --> verify
  verify --> start

  style fix1 fill:#ffe0e0
  style fix2 fill:#ffe0e0
  style fix3 fill:#ffe0e0
  style done fill:#d4edda

この図が示すように、問題発見 → 対処 → 再確認のサイクルを回すことが重要です。

重要ポイントの再確認

EXPLAIN の各列で特に注目すべきポイントをまとめました。

列名最優先チェック項目危険信号理想的な状態
typeアクセス方法ALL, indexconst, eq_ref, ref
rows処理行数10000 以上100 以下
Extra追加処理Using filesort, Using temporaryUsing index
key使用インデックスNULLインデックス名表示

この表をチートシートとして、日々の開発で活用してください。

対処法の優先順位

パフォーマンス問題を発見したときの対処優先順位です。

  1. インデックスの追加・最適化:最も効果が高く、リスクが低い
  2. クエリの書き換え:ロジックを変えずに構文を最適化
  3. 統計情報の更新:定期的なメンテナンスとして実施
  4. テーブル設計の見直し:大規模な改修が必要な場合の最終手段

段階的に改善していくことで、安全にパフォーマンスを向上できます。

実務での活用ポイント

開発フローに組み込む方法をご紹介します。

新しいクエリを書いたら必ず EXPLAIN を実行する習慣をつけましょう。特に、結合やサブクエリを含むクエリは要注意です。本番環境へのデプロイ前に、EXPLAIN ANALYZEで実測値を確認することで、予期しないパフォーマンス問題を未然に防げます。

また、定期的に遅いクエリログ(slow query log)を確認し、問題のあるクエリを EXPLAIN で分析する運用フローを確立すると、継続的な改善が可能になりますね。

EXPLAIN の読み方をマスターすることで、データベースのパフォーマンス問題に素早く対応できるようになります。本記事をチートシートとして活用し、日々の開発に役立てていただければ幸いです。

関連リンク

より深く学びたい方向けの公式ドキュメントと有用なリソースをご紹介します。

これらのリソースを参考に、さらなるパフォーマンスチューニングの知識を深めていきましょう。