EXPLAINコマンドの活用法
EXPLAINはMySQLのSQL実行計画を分析するための強力なツールです。以下の構文で使用できます:
{EXPLAIN | DESCRIBE | DESC}
tbl_name [col_name | wild]
{EXPLAIN | DESCRIBE | DESC}
[explain_type]
{explainable_stmt | FOR CONNECTION connection_id}
{EXPLAIN | DESCRIBE | DESC} ANALYZE select_statement
explain_type: {
FORMAT = format_name
}
format_name: {
TRADITIONAL
| JSON
| TREE
}
explainable_stmt: {
SELECT statement
| TABLE statement
| DELETE statement
| INSERT statement
| REPLACE statement
| UPDATE statement
}
実行例:
EXPLAIN FORMAT=JSON
SELECT o.order_id, o.customer_name, o.order_date,
p.product_name, p.category, p.price,
d.quantity, d.discount
FROM orders o
JOIN order_details d ON o.order_id = d.order_id
JOIN products p ON d.product_id = p.product_id
WHERE o.status = 'completed'
AND o.order_date >= '2023-01-01'
AND p.category = 'electronics';
出力結果の詳細解説
| 項目 | JSON形式での名称 | 説明 |
| id | select_id | クエリの識別子 |
| select_type | - | クエリ種別 |
| table | table_name | テーブル名 |
| partitions | partitions | 一致したパーティション |
| type | access_type | 結合方式 |
| possible_keys | possible_keys | 使用可能なインデックス |
| key | key | 実際に使用されたインデックス |
| key_len | key_length | インデックス長 |
| ref | ref | 参照された列 |
| rows | rows | 推定スキャン行数 |
| filtered | filtered | 条件一致率(%) |
| Extra | - | 追加情報 |
idの詳細
クエリを一意に識別します。複数のid値がある場合、数値が大きいほど先に実行されます。同じidの場合は上から順に実行されます。
select_typeの種類
| 種別 | 意味 |
| SIMPLE | 単純なSELECT(UNIONやサブクエリなし) |
| PRIMARY | 最も外側のクエリ |
| UNION | UNIONの2番目以降のSELECT |
| DEPENDENT UNION | 外部クエリに依存するUNION |
| UNION RESULT | UNIONの結果セット |
| SUBQUERY | サブクエリ内の最初のSELECT |
| DEPENDENT SUBQUERY | 外部クエリに依存するサブクエリ |
| DERIVED | FROM句のサブクエリ(派生テーブル) |
| MATERIALIZED | 物化されたサブクエリ |
| UNCACHEABLE SUBQUERY | 結果をキャッシュできないサブクエリ |
type(結合方式)の詳細
性能順(良い→悪い):
- system:テーブルが1行のみ(システムテーブル相当)
- const:主キーまたはユニークインデックスの等価検索
- eq_ref:結合で各テーブルが1行のみ一致
- ref:インデックスの等価検索(一致行が複数可能)
- fulltext:全文インデックス検索
- ref_or_null:ref + NULL値の検索
- index_merge:複数のインデックスを統合使用
- unique_subquery:ユニークインデックスのサブクエリ
- index_subquery:非ユニークインデックスのサブクエリ
- range:インデックスの範囲検索
- index:インデックス全スキャン
- ALL:テーブル全スキャン(最悪)
Extraフィールドの重要な値
- Using filesort:ORDER BYでインデックスが使用できず、ソートが必要
- Using temporary:GROUP BYやORDER BYで一時テーブルを作成
- Using index:カバリングインデックスを使用(テーブルアクセス不要)
- Using index condition:インデックス条件プッシュダウンを使用
- Using where:WHERE句でフィルタリングを実行
- Using join buffer:結合バッファを使用
- Distinct:DISTINCTを最適化
- Not exists:LEFT JOINを最適化
- Range checked for each record:各レコードで範囲検索を評価
実践的なEXPLAIN分析
以下に実用的な分析例を示します:
-- パフォーマンスが良い例
EXPLAIN SELECT employee_id, name
FROM employees
WHERE emp_no = 'E1001';
-- 結果:
-- type: const (主キー検索)
-- key: PRIMARY
-- rows: 1
-- 改善が必要な例
EXPLAIN SELECT *
FROM sales s
JOIN customers c ON s.customer_id = c.id
WHERE s.sales_date BETWEEN '2023-01-01' AND '2023-12-31'
AND c.region = 'Kanto';
-- 結果例:
-- type: ALL (テーブル全スキャン)
-- Extra: Using where; Using join buffer
拡張EXPLAIN機能
MySQL 8.0以降では、EXPLAIN後にSHOW WARNINGSで詳細情報を確認できます:
EXPLAIN
SELECT p.product_id, p.name,
(SELECT AVG(price) FROM price_history
WHERE product_id = p.product_id) as avg_price
FROM products p
WHERE p.category = 'books';
SHOW WARNINGS;
クエリ性能の見積もり
ディスク検索回数で性能を見積もれます:
大規模テーブルの場合:
検索回数 = log(行数) / log(インデックスブロック長 / 3 * 2 / (インデックス長 + データポインタ長)) + 1
例:500,000行、キー3バイトの場合:
log(500000) / log(1024/3*2/(3+4)) + 1 = 約4回検索
最適化のベストプラクティス
- WHERE句のカラムにインデックスを作成
- 複合インデックスではカーディナリティの高いカラムを先頭に
- SELECT *を避け、必要なカラムのみ指定
- Joyin時は常に結合キーにインデックスを使用
- EXPLAINのtypeがconst、eq_ref、refになるよう最適化
- Extraに「Using filesort」「Using temporary」が表示されないように
インデックス設計のヒント
効果的なインデックス設計例:
-- 悪い例
SELECT * FROM orders
WHERE customer_id = 12345
AND order_date BETWEEN '2023-01-01' AND '2023-12-31'
AND status = 'completed';
-- 良いインデックス
CREATE INDEX idx_customer_date_status
ON orders(customer_id, order_date, status);
インデックス使用確認:
EXPLAIN SELECT order_id, total_amount
FROM orders
WHERE customer_id = 12345
AND order_date >= '2023-01-01'
AND status = 'completed';