MySQLの検索が遅い原因を特定!EXPLAINコマンドを使ったクエリ最適化の具体手順

MySQLのEXPLAINコマンドを使ったクエリ最適化の全体フローを、インデックス設計と実行計画の改善例とともに示したアイキャッチ画像。 データベース

MySQLの検索が遅いと感じたとき、多くの開発者は「インデックスを貼れば解決する」と安易に考えがちです。
しかし、実際にはインデックスが正しく使われていない、結合順序が非効率、サブクエリが過剰にネストされているなど、複数の要因が絡み合っているケースが少なくありません。
闇雲にインデックスを追加しても、書き込み性能が低下したり、ストレージ使用量が増大したりするだけで、検索速度はほとんど改善しないこともあります。

そこで重要になるのが、MySQLがどのようにクエリを実行しているかを可視化することです。
そのための標準的な手段が EXPLAIN コマンドです。
EXPLAIN を使うと、オプティマイザが選択した実行計画(アクセス方法、結合順序、使用するインデックスなど)を確認できます。
この情報を読み解くことで、ボトルネックがどこにあるのかを論理的に特定し、具体的な最適化手順を立てることができます。

  • EXPLAIN の出力項目(type, key, rows, Extra など)を正しく理解し、どの値が「危険信号」なのかを把握する
  • フルテーブルスキャンやテンポラリテーブル生成が発生している箇所を特定し、インデックス設計やクエリ構造の見直しに結びつける
  • 実際のクエリ例に対して EXPLAIN を実行し、改善前後の実行計画を比較しながら、効果的な最適化手順をステップバイステップで確認する

本記事では、MySQLの検索が遅くなる典型的な原因を整理したうえで、EXPLAIN コマンドを使った原因特定とクエリ最適化の具体的な手順を、実践的なクエリ例とともに解説します。
単に「こう書けば速くなる」というテクニック紹介に留まらず、なぜその書き方が効くのか、オプティマイザの挙動と紐づけながら理解できるように構成しています。
読み終える頃には、EXPLAIN の出力を自分で読み解き、検索性能のボトルネックを論理的に突き止められるようになることを目指します。

  1. MySQLの検索が遅い原因を特定!EXPLAINコマンドを使ったクエリ最適化の具体手順
    1. EXPLAINでわかることと、遅いクエリの典型的なパターン
    2. EXPLAINを使った原因特定と最適化の基本フロー
    3. 具体例:インデックスが使われていないクエリの改善
    4. まとめ:EXPLAINを軸にしたMySQLクエリ最適化の考え方
  2. MySQLの検索が遅くなる主な原因とボトルネック
    1. EXPLAINでわかることと、遅いクエリの典型的なパターン
    2. EXPLAINを使った原因特定と最適化の基本フロー
    3. 具体例:インデックスが使われていないクエリの改善
    4. まとめ:EXPLAINを軸にしたMySQLクエリ最適化の考え方
  3. EXPLAINコマンドとは?実行計画を可視化する基本
    1. EXPLAINの各項目を読み解く:type, key, rows, Extraの意味
    2. フルテーブルスキャン(type: ALL)が発生しているケースの特定
    3. 非効率な結合やサブクエリが遅さの原因になっているケース
  4. インデックス設計の見直しで検索速度を改善する具体手順
    1. WHERE句とORDER BY句に効くインデックスの設計方法
    2. カバリングインデックスを活用してテーブルアクセスを減らす
  5. クエリ構造の見直し:JOINの順序とサブクエリの最適化
    1. サブクエリからJOINへの書き換えでパフォーマンスを向上
    2. LIMIT句やOFFSET句の使い方とインデックスの関係
  6. 実際のクエリ例でEXPLAINを実行し、改善前後を比較する
    1. EXPLAIN ANALYZEで実際の実行時間を確認する方法
  7. スロークエリログとEXPLAINを組み合わせたボトルネック特定
    1. スロークエリログの有効化と基本的な使い方
    2. スロークエリログから遅いクエリを抽出し、EXPLAINで実行計画を確認する
    3. EXPLAINの結果に基づいた具体的な改善策の検討
    4. まとめ:スロークエリログとEXPLAINの組み合わせによる持続的な性能改善
  8. まとめ:EXPLAINコマンドを使ったMySQLクエリ最適化の実践フロー
    1. EXPLAINを使ったMySQLクエリ最適化の実践フロー
    2. EXPLAINを読み解くうえでのポイント
    3. 持続的な性能改善に向けて

MySQLの検索が遅い原因を特定!EXPLAINコマンドを使ったクエリ最適化の具体手順

MySQLのEXPLAINコマンドを使ったクエリ最適化の全体像を示す図。実行計画とインデックス設計の関係を可視化。

MySQLでWebアプリケーションを運用していると、「特定の画面だけ表示が遅い」「管理画面の一覧取得が重い」といった性能問題に直面することがあります。
多くの場合、その原因はデータベースの検索クエリにあり、インデックスが正しく使われていない結合順序が非効率サブクエリが過剰にネストされているといった要因が複合的に絡み合っています。
闇雲にインデックスを追加しても、書き込み性能の低下やストレージ使用量の増大を招くだけで、検索速度がほとんど改善しないケースも少なくありません。

そこで重要になるのが、MySQLがどのようにクエリを実行しているかを可視化することです。
そのための標準的な手段が EXPLAIN コマンドです。
EXPLAIN を使うと、MySQLのオプティマイザが選択した実行計画(どのインデックスを使うか、どの順序でテーブルを結合するか、どの程度の行数を読み込むと見積もっているか)を確認できます。
この情報を読み解くことで、ボトルネックがどこにあるのかを論理的に特定し、具体的な最適化手順を立てることができます。

EXPLAINでわかることと、遅いクエリの典型的なパターン

EXPLAIN を実行すると、以下のような情報が得られます。

  • type:どのような方法で行にアクセスするか(フルテーブルスキャン、インデックススキャンなど)
  • key:実際に使用されたインデックス名
  • rows:処理対象と見積もられた行数
  • Extra:追加情報(Using temporary, Using filesort など)

このうち、特に注意すべきは typeExtra です。
typeALL になっている場合、そのテーブルに対してフルテーブルスキャンが発生していることを意味します。
テーブルサイズが大きいと、この操作が検索遅延の主因になります。
また、ExtraUsing temporaryUsing filesort が表示されている場合、一時テーブルの生成やソート処理が発生しており、メモリやディスクI/Oに負荷がかかっている可能性があります。

EXPLAINを使った原因特定と最適化の基本フロー

EXPLAIN を使ったクエリ最適化は、以下のようなステップで進めると効率的です。

  1. 遅いクエリを特定する(スロークエリログやアプリケーションのログから)
  2. そのクエリに対して EXPLAIN を実行し、実行計画を確認する
  3. typeALL になっているテーブルや、rows が極端に大きい箇所を重点的にチェックする
  4. ExtraUsing temporaryUsing filesort が出ている場合、クエリ構造やインデックス設計を見直す
  5. インデックス追加やクエリ書き換えを行い、再度 EXPLAIN を実行して改善を確認する

このフローを繰り返すことで、「どの変更がどの指標を改善したか」を定量的に把握できます。
経験則だけで「この書き方は速い」と判断するのではなく、実行計画の変化に基づいて最適化を進めることが重要です。

具体例:インデックスが使われていないクエリの改善

たとえば、以下のようなクエリが遅いとします。

SELECT * FROM orders
WHERE user_id = 123
  AND created_at BETWEEN '2025-01-01' AND '2025-12-31';

このクエリに対して EXPLAIN を実行したところ、typeALL で、keyNULL になっていたとします。
これは、user_idcreated_at に適切なインデックスが存在せず、orders テーブル全体をスキャンしていることを意味します。

この場合、(user_id, created_at) に対する複合インデックスを追加することで、typerefrange に変わり、rows が大幅に減少する可能性があります。
改善後の EXPLAIN を再度確認し、実際にインデックスが使われていることを確認するのが、実践的な最適化の流れです。

まとめ:EXPLAINを軸にしたMySQLクエリ最適化の考え方

MySQLの検索が遅い原因を特定するうえで、EXPLAIN コマンドは不可欠なツールです。
単に「クエリを書き換える」「インデックスを追加する」という対応に留まらず、実行計画の変化を確認しながら、論理的にボトルネックを突き止めることが、持続可能な性能改善につながります。
本記事では、この考え方を軸に、具体的なクエリ例と EXPLAIN の出力を交えながら、MySQLの検索性能を向上させる手順を詳しく解説していきます。

MySQLの検索が遅くなる主な原因とボトルネック

MySQLで検索が遅くなる典型的な原因(フルテーブルスキャン、非効率な結合など)をまとめた図。

MySQLでWebアプリケーションを運用していると、「特定の画面だけ表示が遅い」「管理画面の一覧取得が重い」といった性能問題に直面することがあります。
多くの場合、その原因はデータベースの検索クエリにあり、インデックスが正しく使われていない結合順序が非効率サブクエリが過剰にネストされているといった要因が複合的に絡み合っています。
闇雲にインデックスを追加しても、書き込み性能の低下やストレージ使用量の増大を招くだけで、検索速度がほとんど改善しないケースも少なくありません。

そこで重要になるのが、MySQLがどのようにクエリを実行しているかを可視化することです。
そのための標準的な手段が EXPLAIN コマンドです。
EXPLAIN を使うと、MySQLのオプティマイザが選択した実行計画(どのインデックスを使うか、どの順序でテーブルを結合するか、どの程度の行数を読み込むと見積もっているか)を確認できます。
この情報を読み解くことで、ボトルネックがどこにあるのかを論理的に特定し、具体的な最適化手順を立てることができます。

EXPLAINでわかることと、遅いクエリの典型的なパターン

EXPLAIN を実行すると、以下のような情報が得られます。

  • type:どのような方法で行にアクセスするか(フルテーブルスキャン、インデックススキャンなど)
  • key:実際に使用されたインデックス名
  • rows:処理対象と見積もられた行数
  • Extra:追加情報(Using temporary, Using filesort など)

このうち、特に注意すべきは typeExtra です。
typeALL になっている場合、そのテーブルに対してフルテーブルスキャンが発生していることを意味します。
テーブルサイズが大きいと、この操作が検索遅延の主因になります。
また、ExtraUsing temporaryUsing filesort が表示されている場合、一時テーブルの生成やソート処理が発生しており、メモリやディスクI/Oに負荷がかかっている可能性があります。

EXPLAINを使った原因特定と最適化の基本フロー

EXPLAIN を使ったクエリ最適化は、以下のようなステップで進めると効率的です。

  1. 遅いクエリを特定する(スロークエリログやアプリケーションのログから)
  2. そのクエリに対して EXPLAIN を実行し、実行計画を確認する
  3. typeALL になっているテーブルや、rows が極端に大きい箇所を重点的にチェックする
  4. ExtraUsing temporaryUsing filesort が出ている場合、クエリ構造やインデックス設計を見直す
  5. インデックス追加やクエリ書き換えを行い、再度 EXPLAIN を実行して改善を確認する

このフローを繰り返すことで、「どの変更がどの指標を改善したか」を定量的に把握できます。
経験則だけで「この書き方は速い」と判断するのではなく、実行計画の変化に基づいて最適化を進めることが重要です。

具体例:インデックスが使われていないクエリの改善

たとえば、以下のようなクエリが遅いとします。

SELECT * FROM orders
WHERE user_id = 123
  AND created_at BETWEEN '2025-01-01' AND '2025-12-31';

このクエリに対して EXPLAIN を実行したところ、typeALL で、keyNULL になっていたとします。
これは、user_idcreated_at に適切なインデックスが存在せず、orders テーブル全体をスキャンしていることを意味します。

この場合、(user_id, created_at) に対する複合インデックスを追加することで、typerefrange に変わり、rows が大幅に減少する可能性があります。
改善後の EXPLAIN を再度確認し、実際にインデックスが使われていることを確認するのが、実践的な最適化の流れです。

まとめ:EXPLAINを軸にしたMySQLクエリ最適化の考え方

MySQLの検索が遅い原因を特定するうえで、EXPLAIN コマンドは不可欠なツールです。
単に「クエリを書き換える」「インデックスを追加する」という対応に留まらず、実行計画の変化を確認しながら、論理的にボトルネックを突き止めることが、持続可能な性能改善につながります。
本記事では、この考え方を軸に、具体的なクエリ例と EXPLAIN の出力を交えながら、MySQLの検索性能を向上させる手順を詳しく解説していきます。

EXPLAINコマンドとは?実行計画を可視化する基本

MySQLのEXPLAINコマンドの基本的な使い方と出力項目(type, key, rows, Extraなど)の説明図。

MySQLでクエリが遅いとき、多くの開発者は「インデックスを追加すれば解決する」と考えがちです。
しかし、実際にはインデックスが使われていない、結合順序が非効率、サブクエリが過剰にネストされているなど、複数の要因が絡み合っているケースが少なくありません。
そこで重要になるのが、MySQLがどのようにクエリを実行しているかを可視化することです。
そのための標準的な手段が EXPLAIN コマンドです。

EXPLAIN は、SELECT文や一部のDML文の前に付けることで、MySQLのオプティマイザがどのような実行計画を立てているかを教えてくれるコマンドです。
具体的には、「どのテーブルをどの順序でアクセスするか」「どのインデックスを使うか」「どの程度の行数を読み込むと見積もっているか」といった情報を、表形式で出力します。
この情報を読み解くことで、ボトルネックがどこにあるのかを論理的に特定し、具体的な最適化手順を立てることができます。

EXPLAINの各項目を読み解く:type, key, rows, Extraの意味

EXPLAIN の出力には多くの列がありますが、クエリ最適化の観点で特に重要なのは type, key, rows, Extra です。

  • type:行へのアクセス方法を表します。ALL はフルテーブルスキャン、index はインデックス全体のスキャン、range は範囲スキャン、refeq_ref は等価条件によるインデックス参照を意味します。ALL は最もコストが高く、テーブルサイズが大きいと性能劣化の主因になります
  • key:実際に使用されたインデックス名です。NULL の場合はインデックスが使われていません
  • rows:そのステップで処理されると見積もられた行数です。この値が大きいほど、多くの行を読み込む必要があることを意味します
  • Extra:追加の実行情報です。Using temporary は一時テーブル生成、Using filesort はファイルソート(メモリ外ソート)が発生していることを示し、メモリやディスクI/Oに負荷がかかっている可能性があります

これらの項目を組み合わせて見ることで、「どのテーブルがどの程度のコストをかけているか」を定量的に把握できます。

フルテーブルスキャン(type: ALL)が発生しているケースの特定

typeALL になっている行は、そのテーブルに対してフルテーブルスキャンが発生していることを意味します。
テーブルサイズが大きい場合、この操作が検索遅延の主因になります。

たとえば、以下のようなクエリを考えます。

SELECT * FROM users WHERE status = 'active';

このクエリに対して EXPLAIN を実行した結果、typeALLkeyNULL になっていたとします。
これは、status 列にインデックスが存在せず、users テーブル全体をスキャンしていることを示します。
この場合、status 列にインデックスを追加することで、typeref などに変わり、rows が大幅に減少する可能性があります。

非効率な結合やサブクエリが遅さの原因になっているケース

複数のテーブルを結合するクエリや、サブクエリを含むクエリでは、結合順序やサブクエリの評価方法が性能に大きく影響します。

たとえば、以下のようなクエリが遅いとします。

SELECT *
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE u.created_at > '2024-01-01';

EXPLAIN の結果を見ると、users テーブルの typeALL で、rows が非常に大きいことがわかったとします。
これは、users テーブルに対してフルスキャンが発生し、その結果を orders と結合していることを意味します。
この場合、users.created_at にインデックスを追加する、あるいはクエリの構造を見直すことで、結合前に絞り込む行数を減らせる可能性があります。

また、サブクエリが EXISTSIN の中で使われている場合、EXPLAINExtraUsing where; Using temporary などが表示されることがあります。
これは、サブクエリの結果を一時テーブルに格納していることを示し、メモリやI/Oに負荷がかかっている可能性があります。
このような場合、サブクエリをJOINに書き換えることで、実行計画が改善することがあります。

EXPLAIN の出力を丁寧に読み解くことで、こうした非効率な結合やサブクエリの構造を特定し、具体的な改善策を検討できます。

インデックス設計の見直しで検索速度を改善する具体手順

MySQLのインデックス設計のベストプラクティス(カバリングインデックス、複合インデックスなど)を解説する図。

MySQLで検索が遅いとき、多くの場合その原因はインデックス設計にあります。
インデックスは「検索を速くする魔法の道具」ではなく、どの列に、どの順序で、どのような目的で貼るかを論理的に設計して初めて効果を発揮します。
闇雲にインデックスを追加すると、書き込み性能の低下やストレージ使用量の増大を招くだけで、検索速度がほとんど改善しないことも少なくありません。

そこで重要になるのが、EXPLAIN コマンドで実行計画を確認しながら、どのインデックスが実際に使われているかを把握することです。
typeALL のままだったり、keyNULL のままだったりする場合は、インデックスが設計されていないか、あるいはクエリの書き方とインデックスの順序が噛み合っていない可能性があります。
本節では、WHERE句とORDER BY句に効くインデックスの設計方法と、カバリングインデックスを活用してテーブルアクセスを減らす具体手順を解説します。

WHERE句とORDER BY句に効くインデックスの設計方法

WHERE句とORDER BY句を効率よく処理するには、インデックスの列順序をクエリの条件順と一致させることが重要です。
たとえば、以下のようなクエリを考えます。

SELECT * FROM orders
WHERE user_id = 123
  AND created_at BETWEEN '2025-01-01' AND '2025-12-31'
ORDER BY created_at DESC;

このクエリに対して、(user_id, created_at) という複合インデックスを貼ると、WHERE句の等価条件(user_id = 123)と範囲条件(created_at BETWEEN ...)を効率的に処理できます。
さらに、ORDER BY句の created_at DESC もインデックスの順序を利用できるため、ファイルソート(Using filesort)が発生しにくくなります。

一方で、インデックスを (created_at, user_id) の順序で貼った場合、user_id がWHERE句の2番目の条件であるため、インデックスの先頭列である created_at だけが使われ、user_id による絞り込みが十分に効かないことがあります。
このように、インデックスの列順序はクエリの条件順と一致させることが基本です。

カバリングインデックスを活用してテーブルアクセスを減らす

カバリングインデックスとは、クエリで必要な列をすべてインデックスに含めることで、テーブル本体へのアクセスを避ける手法です。
たとえば、以下のクエリを考えます。

SELECT user_id, created_at, amount
FROM orders
WHERE user_id = 123
  AND status = 'completed';

このクエリに対して、(user_id, status) だけのインデックスを貼っている場合、user_idstatus で行を絞り込んだ後、amountcreated_at を取得するためにテーブル本体(データ行)にアクセスする必要があります。
これが EXPLAINExtra に表示される「Using index condition」や、単にテーブルアクセスが発生している状態です。

一方で、(user_id, status, created_at, amount) のように、SELECT句で参照する列をすべてインデックスに含めると、インデックスだけで必要なデータが揃うため、テーブルアクセスが不要になります。
この状態は EXPLAINExtra に「Using index」と表示され、I/O負荷が大幅に軽減されます。

ただし、カバリングインデックスは列数が増えるため、書き込み時のオーバーヘッドやストレージ使用量が増える点に注意が必要です。
頻繁に実行される読み取り専用のクエリや、パフォーマンスがクリティカルな画面に対して重点的に適用するのが現実的な戦略です。

EXPLAINExtra に「Using index」が表示されているかどうかを確認しながら、カバリングインデックスを設計することで、テーブルアクセスを減らし、検索速度を効果的に改善できます。

クエリ構造の見直し:JOINの順序とサブクエリの最適化

JOIN順序やサブクエリの書き方を変えることで、EXPLAINのrowsやExtraが改善する様子を示す図。

MySQLで検索が遅い場合、インデックス設計だけでなく、クエリそのものの構造が性能に大きく影響していることがあります。
特に、複数のテーブルを結合するクエリや、サブクエリを多用したクエリでは、結合順序やサブクエリの評価方法が実行計画に直結します。
EXPLAIN コマンドで実行計画を確認しながら、クエリ構造を見直すことで、インデックス追加だけでは解決しなかったボトルネックを解消できるケースも少なくありません。

本節では、サブクエリからJOINへの書き換えによるパフォーマンス向上と、LIMIT句・OFFSET句の使い方とインデックスの関係について、具体的なクエリ例と EXPLAIN の観点から解説します。

サブクエリからJOINへの書き換えでパフォーマンスを向上

サブクエリは直感的に書きやすい一方で、MySQLのオプティマイザが最適な実行計画を立てにくい場合があります。
特に INEXISTS の中にサブクエリを書くと、サブクエリの結果を一時テーブルに格納してから結合するような挙動になることがあり、EXPLAINExtraUsing temporaryUsing filesort が表示されることがあります。

たとえば、以下のようなクエリを考えます。

SELECT *
FROM orders
WHERE user_id IN (
  SELECT id FROM users WHERE status = 'active'
);

このクエリを EXPLAIN で確認すると、users テーブルに対するサブクエリが一時テーブルとして評価され、その結果を orders と結合している可能性があります。
この場合、サブクエリをJOINに書き換えることで、実行計画が改善することがあります。

SELECT o.*
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE u.status = 'active';

書き換え後のクエリでは、usersorders の結合順序やインデックス利用の自由度が高くなり、EXPLAINtyperows が改善する可能性があります。
もちろん、常にJOINが優れているわけではなく、データ分布やインデックス状況によってはサブクエリの方が適している場合もあります。
重要なのは、EXPLAIN で実行計画を比較しながら、どちらが効率的かを判断することです。

LIMIT句やOFFSET句の使い方とインデックスの関係

LIMIT句やOFFSET句は、ページネーションなどでよく使われますが、「先に多くの行を読み込んでから、そのうちの一部を返す」という挙動になることがあります。
特にOFFSETが大きい場合、その分だけ多くの行をスキャンする必要があり、パフォーマンス劣化の原因になります。

たとえば、以下のようなクエリを考えます。

SELECT * FROM articles
ORDER BY created_at DESC
LIMIT 10 OFFSET 10000;

このクエリでは、まず created_at でソートされた全行を読み込み、そのうちの10000行をスキップしてから10行を返す、という処理が行われます。
EXPLAIN で確認すると、typeALLExtraUsing filesort が表示される可能性があります。

このような場合、created_at にインデックスを貼ることでソート負荷を軽減できますが、OFFSETによるスキップ処理そのものは避けられません。
より根本的な改善策として、「最後に取得した行の位置を覚えておき、その次の行から取得する」方式(いわゆるカーソルベースページネーション)に切り替える方法があります。

SELECT * FROM articles
WHERE created_at < '2025-01-01 00:00:00'
ORDER BY created_at DESC
LIMIT 10;

この方式では、OFFSETを使わずに「前回取得した最後の行の created_at より古い行」を取得するため、スキャンする行数を大幅に減らせます。
created_at にインデックスが貼られていれば、typerange などになり、rows も小さく抑えられます。

LIMIT/OFFSETを使う際は、EXPLAINrows がどの程度見積もられているかを確認し、OFFSETが大きくなったときのスキャン行数が許容範囲内かどうかをチェックすることが重要です。
許容できない場合は、カーソルベースページネーションへの移行を検討する価値があります。

実際のクエリ例でEXPLAINを実行し、改善前後を比較する

実際のMySQLクエリに対してEXPLAINを実行し、改善前後の実行計画を並べて比較する図。

MySQLの検索が遅い原因を特定し、クエリを最適化するうえで、EXPLAIN コマンドは不可欠なツールです。
しかし、単に EXPLAIN を実行するだけでは不十分で、改善前後の実行計画を比較し、どの変更がどの指標を改善したかを定量的に把握することが重要です。
本節では、実際のクエリ例に対して EXPLAIN を実行し、改善前後を比較する流れを解説します。
さらに、MySQL 8.0以降で利用できる EXPLAIN ANALYZE を使った実際の実行時間の確認方法についても触れます。

まず、遅いと感じているクエリを特定します。
スロークエリログやアプリケーションのログから、実行時間が長いクエリをピックアップします。
たとえば、以下のようなクエリが遅いとします。

SELECT u.name, o.created_at, o.amount
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE u.status = 'active'
  AND o.created_at BETWEEN '2025-01-01' AND '2025-12-31'
ORDER BY o.created_at DESC;

このクエリに対して EXPLAIN を実行し、実行計画を確認します。
仮に、users テーブルの typeALL で、orders テーブルの typeref になっていたとします。
これは、users テーブルに対してフルテーブルスキャンが発生し、その結果を orders と結合していることを意味します。
rows の値も大きければ、この部分がボトルネックになっている可能性が高いです。

次に、インデックス設計やクエリ構造を見直します。
たとえば、users.status にインデックスを追加する、あるいは (user_id, created_at) に対する複合インデックスを追加するなど、EXPLAIN の結果に基づいて改善策を検討します。
改善後のクエリに対して再度 EXPLAIN を実行し、typerefrange に変わったか、rows が減少したか、ExtraUsing filesortUsing temporary が消えたかを確認します。

このように、「どの変更がどの指標を改善したか」EXPLAIN の出力で追跡することで、経験則に頼らない論理的な最適化が可能になります。

EXPLAIN ANALYZEで実際の実行時間を確認する方法

EXPLAIN はあくまで「オプティマイザが立てた計画」であり、実際の実行時間を直接示すものではありません。
MySQL 8.0以降では、EXPLAIN ANALYZE を使うことで、実際の実行時間や各ステップのコストをより詳細に確認できます。

EXPLAIN ANALYZE は、クエリを実際に実行しながら、各ステップの処理時間や処理行数を計測し、その結果を出力します。
たとえば、以下のように使います。

EXPLAIN ANALYZE
SELECT u.name, o.created_at, o.amount
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE u.status = 'active'
  AND o.created_at BETWEEN '2025-01-01' AND '2025-12-31'
ORDER BY o.created_at DESC;

出力には、各ステップの実際の実行時間(ミリ秒単位)や、処理された行数が表示されます。
これにより、EXPLAIN で「コストが高そう」と判断した箇所が、実際にどの程度の時間を消費しているかを確認できます。

EXPLAIN ANALYZE を使う際の注意点として、クエリを実際に実行するため、本番環境で実行すると負荷やロック競合を引き起こす可能性があります。
可能であれば、ステージング環境やローカルのテスト環境で実行することをおすすめします。

改善前後のクエリに対して EXPLAIN ANALYZE を実行し、実際の実行時間を比較することで、EXPLAIN だけでは見えなかった微細なボトルネック(たとえば、特定の結合順序でのロック待ち時間など)を発見できることもあります。
EXPLAINEXPLAIN ANALYZE を組み合わせることで、MySQLの検索性能をより深く理解し、効果的な最適化を進めることができます。

スロークエリログとEXPLAINを組み合わせたボトルネック特定

スロークエリログで遅いクエリを特定し、EXPLAINで原因を分析する流れを説明する図。

MySQLでアプリケーションの検索性能を改善する際、「どのクエリが実際に遅いのか」を正確に把握することが第一歩です。
開発環境では問題なく動いていたクエリが、本番環境のデータ量や負荷状況によって急に遅くなることは珍しくありません。
そのような状況で頼りになるのが、MySQLのスロークエリログです。
スロークエリログは、設定した閾値(しきい値)以上の時間がかかったクエリをすべて記録する機能で、実際の運用環境で発生している遅いクエリを網羅的に把握できます。

ただし、スロークエリログだけでは「どのクエリが遅いか」はわかっても、「なぜ遅いのか」まではわかりません。
そこで、スロークエリログで遅いクエリを特定し、そのクエリに対して EXPLAIN を実行して実行計画を確認するという組み合わせが有効です。
この2つを組み合わせることで、ボトルネックを論理的に特定し、具体的な改善策を立てることができます。

スロークエリログの有効化と基本的な使い方

スロークエリログを有効にするには、MySQLの設定ファイル(my.cnfmy.ini)で以下のような設定を追加します。

slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
  • slow_query_log = 1:スロークエリログを有効にします
  • slow_query_log_file:ログを出力するファイルパスを指定します
  • long_query_time = 2:2秒以上かかったクエリをスロークエリとして記録します。運用環境の負荷に応じて調整します

設定を反映後、一定期間運用すると、slow_query_log_file に指定したパスに遅いクエリが記録されます。
ログには、クエリの実行時間、ロック時間、返却行数などの情報が含まれます。
このログを解析することで、どのクエリが頻繁に遅くなっているかどの時間帯に負荷が集中しているかを把握できます。

スロークエリログから遅いクエリを抽出し、EXPLAINで実行計画を確認する

スロークエリログには生のSQL文が記録されるため、そのまま EXPLAIN に渡すことができます。
たとえば、ログに以下のようなクエリが頻繁に出現していたとします。

SELECT u.name, o.created_at, o.amount
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE u.status = 'active'
  AND o.created_at BETWEEN '2025-01-01' AND '2025-12-31'
ORDER BY o.created_at DESC;

このクエリに対して EXPLAIN を実行し、実行計画を確認します。
仮に、users テーブルの typeALL で、orders テーブルの typeref になっていたとします。
これは、users テーブルに対してフルテーブルスキャンが発生し、その結果を orders と結合していることを意味します。
rows の値も大きければ、この部分がボトルネックになっている可能性が高いです。

このように、スロークエリログで「実際に遅いクエリ」を特定し、EXPLAIN で「なぜ遅いのか(どのテーブルがどのようにアクセスされているか)」を確認する流れが、ボトルネック特定の基本です。

EXPLAINの結果に基づいた具体的な改善策の検討

EXPLAIN の結果からボトルネックが特定できたら、具体的な改善策を検討します。
代表的なパターンと対策は以下の通りです。

  • typeALL になっているテーブルがある場合:WHERE句やJOIN条件に使われている列にインデックスを追加する、あるいは既存のインデックスが使われていない原因(関数を使った条件など)を解消する
  • ExtraUsing temporaryUsing filesort が表示されている場合:ORDER BYやGROUP BYの列順序とインデックスの順序を合わせる、あるいはクエリ構造を見直して一時テーブル生成を避ける
  • サブクエリが多用されている場合:サブクエリをJOINに書き換え、実行計画が改善するか確認する

改善策を適用した後は、再度 EXPLAIN を実行して typerowsExtra がどのように変化したかを確認します。
可能であれば、ステージング環境で実際の実行時間も計測し、スロークエリログに再び記録されないことを確認します。

まとめ:スロークエリログとEXPLAINの組み合わせによる持続的な性能改善

スロークエリログと EXPLAIN を組み合わせることで、「実際に遅いクエリ」を特定し、「なぜ遅いのか」を実行計画レベルで理解し、「どの改善が効いたのか」を定量的に確認するという、持続的な性能改善のサイクルを回すことができます。
単発のチューニングに留まらず、定期的にスロークエリログを確認し、EXPLAIN で実行計画をチェックする習慣をつけることで、アプリケーション全体の検索性能を安定させることができます。

まとめ:EXPLAINコマンドを使ったMySQLクエリ最適化の実践フロー

MySQLの検索遅延をEXPLAINで特定し、インデックス設計とクエリ構造の見直しで改善する全体フロー図。

MySQLで検索が遅いと感じたとき、多くの開発者は「インデックスを追加すれば解決する」と考えがちです。
しかし、実際にはインデックスが正しく使われていない、結合順序が非効率、サブクエリが過剰にネストされているなど、複数の要因が絡み合っているケースが少なくありません。
闇雲にインデックスを追加しても、書き込み性能の低下やストレージ使用量の増大を招くだけで、検索速度がほとんど改善しないこともあります。

そこで重要になるのが、MySQLがどのようにクエリを実行しているかを可視化することです。
そのための標準的な手段が EXPLAIN コマンドです。
EXPLAIN を使うと、オプティマイザが選択した実行計画(アクセス方法、結合順序、使用するインデックスなど)を確認できます。
この情報を読み解くことで、ボトルネックがどこにあるのかを論理的に特定し、具体的な最適化手順を立てることができます。

本記事では、MySQLの検索が遅くなる典型的な原因を整理したうえで、EXPLAIN コマンドを使った原因特定とクエリ最適化の具体的な手順を、実践的なクエリ例とともに解説してきました。
単に「こう書けば速くなる」というテクニック紹介に留まらず、なぜその書き方が効くのか、オプティマイザの挙動と紐づけながら理解できるように構成しています。
読み終える頃には、EXPLAIN の出力を自分で読み解き、検索性能のボトルネックを論理的に突き止められるようになることを目指します。

EXPLAINを使ったMySQLクエリ最適化の実践フロー

EXPLAIN を軸にしたMySQLクエリ最適化は、以下のようなステップで進めると効率的です。

  1. 遅いクエリの特定:スロークエリログやアプリケーションのログから、実際に遅いクエリを特定します。どの画面・どの機能で遅さを感じているかを明確にします
  2. EXPLAINの実行:特定したクエリに対して EXPLAIN を実行し、実行計画を確認します。特に type, key, rows, Extra に注目します
  3. ボトルネックの特定typeALL になっているテーブル、rows が極端に大きい箇所、ExtraUsing temporaryUsing filesort が表示されている箇所を重点的にチェックします
  4. インデックス設計の見直し:WHERE句やORDER BY句に効くインデックスを設計し、必要に応じてカバリングインデックスを活用してテーブルアクセスを減らします
  5. クエリ構造の見直し:非効率な結合順序やサブクエリをJOINに書き換える、LIMIT/OFFSETの使い方を見直すなど、クエリそのものの構造を改善します
  6. 改善前後の比較:改善後のクエリに対して再度 EXPLAIN を実行し、typerows, Extra がどのように変化したかを確認します。可能であれば EXPLAIN ANALYZE で実際の実行時間も比較します
  7. スロークエリログでの検証:改善後のクエリがスロークエリログに再び記録されないことを確認し、実際の運用環境での性能改善を検証します

このフローを繰り返すことで、「どの変更がどの指標を改善したか」を定量的に把握できます。
経験則だけで「この書き方は速い」と判断するのではなく、実行計画の変化に基づいて最適化を進めることが重要です。

EXPLAINを読み解くうえでのポイント

EXPLAIN の出力を読み解く際には、以下のポイントを意識すると良いでしょう。

  • typeALL の行はフルテーブルスキャンが発生している可能性が高く、テーブルサイズが大きいと性能劣化の主因になります
  • keyNULL の場合はインデックスが使われていないことを意味します。WHERE句やJOIN条件に使われている列にインデックスが存在するか確認します
  • rows は処理対象と見積もられた行数です。この値が大きいステップほど、ボトルネック候補になります
  • ExtraUsing temporaryUsing filesort が表示されている場合、一時テーブル生成やソート処理が発生しており、メモリやディスクI/Oに負荷がかかっている可能性があります

これらの情報を組み合わせて、「どのテーブルがどの程度のコストをかけているか」を定量的に把握し、重点的に改善するのが EXPLAIN を使った最適化の基本です。

持続的な性能改善に向けて

MySQLの検索性能改善は、一度きりの作業ではなく、継続的な監視と改善のサイクルです。
スロークエリログを定期的に確認し、EXPLAIN で実行計画をチェックする習慣をつけることで、アプリケーションの成長に伴うデータ量の増加やクエリの複雑化にも柔軟に対応できます。

EXPLAIN コマンドは、そのサイクルを回すための強力なツールです。
本記事で紹介した実践フローを参考に、実際のクエリに対して EXPLAIN を実行し、実行計画を読み解く練習を重ねることで、MySQLの検索性能を論理的に向上させることができるでしょう。

コメント

タイトルとURLをコピーしました