Claude Media
Claude CodeでMySQLのスロークエリを読み、インデックス案を出させる手順

Claude CodeでMySQLのスロークエリを読み、インデックス案を出させる手順

スロークエリログをmysqldumpslowで集約し、claude -pにEXPLAINと一緒に渡してインデックス案を出させる流れです。type=ALLとUsing filesortの読み方も扱います。

MySQLのスロークエリログは、そのままclaude -pに流し込むより、mysqldumpslowで集約してから渡すほうが扱いやすくなります。同じ形のクエリが1行にまとまり、リテラル値もNと'S'に置き換わるためです。そのうえでEXPLAINの結果を添えれば、Claude Codeにインデックスの候補を出させる材料が揃います。

この記事では、ログの出し方から集約、claude -pへの渡し方、EXPLAINの読み方、提案を採用する前の検証までを順に書きます。接続はMySQLへの直接ではなく、ファイルとテキストで渡す前提です。読み取り専用で実DBに繋ぐ方法はMySQL MCPサーバーの導入手順にあります。

スロークエリログに残るもの、残らないもの

スロークエリログは、long_query_time秒を超えて実行され、かつmin_examined_row_limit行以上を調べたSQLの記録です。long_query_timeの既定値は10秒、最小値は0です。マイクロ秒まで指定できます。

注意したいのは、ログが既定で無効な点です。有効にするにはslow_query_logを1にします。ファイル名を決めなければ、データディレクトリにホスト名-slow.logができます。実行中に切り替えるときは、グローバル変数のslow_query_logとslow_query_log_fileを使います。

SET GLOBAL slow_query_log = 1;
SET GLOBAL long_query_time = 1;
SHOW VARIABLES LIKE 'slow_query_log_file';

ログに出ないクエリがあることも押さえておきます。管理系の文(ALTER TABLEやCREATE INDEXなど)は既定では記録されません。インデックスを使わないクエリも、既定では対象外です。後者を拾うにはlog_queries_not_using_indexesを有効にしますが、ログが急に膨らむことがあります。量を抑えたいときはlog_throttle_queries_not_using_indexesで、1分あたりの記録数に上限をかけます。

記録されるエントリの先頭には#で始まる行が付き、次の項目が入ります。

項目意味
Query_time意味実行時間(秒)
Lock_time意味ロック取得にかかった時間(秒)
Rows_sent意味クライアントに返した行数
Rows_examined意味サーバー層が調べた行数

log_slow_extraを有効にすると、ファイル出力にThread_idやRead_rnd_next、Sort_merge_passes、Created_tmp_disk_tablesといった項目が加わります。

ここで効いてくるのがRows_sentとRows_examinedの差です。10行しか返していないのに数十万行を調べているクエリは、インデックスで絞れていない可能性が高く、最初に見る価値があります。

mysqldumpslowで集約してから渡す理由

ログをそのまま渡すと、同じクエリが値違いで何百回も並びます。トークンを食うだけでなく、「どれが一番効くか」の判断材料としても粗くなります。

mysqldumpslowはMySQLに付属するツールで、値だけが違うクエリを1つにまとめます。数値はN、文字列は'S'に置き換わります。出力には件数(Count)、平均時間と合計時間(Time)、ロック時間(Lock)、返した行数(Rows)が並びます。

Count: 3  Time=2.53s (7s)  Lock=0.00s (0s)  Rows=0.0 (0), root[root]@localhost
insert into t2 select * from t1 limit N

上の2行はMySQL公式マニュアルに載っている出力の形です。括弧の外が平均、括弧の中が合計を表します。

並べ替えと件数の絞り込みは、次のオプションで決めます。

オプション働き
-s t働き合計時間で並べ替え(既定はat、平均時間)
-s c働き件数で並べ替え
-s r働き返した行数で並べ替え
-t 10働き上位10件だけ表示
-g パターン働きパターンに一致するクエリだけ対象にする
-a働き数値と文字列を抽象化しない

「遅い1本」よりも「そこそこ遅いが毎秒呼ばれる1本」のほうがサーバーを圧迫することがあります。平均時間順の既定のままにせず、合計時間順(-s t)と件数順(-s c)の両方を取っておくと、見落としが減ります。

mysqldumpslow -s t -t 10 /var/lib/mysql/db1-slow.log > top-total.txt
mysqldumpslow -s c -t 10 /var/lib/mysql/db1-slow.log > top-count.txt

集約にはもう1つ利点があります。値がNと'S'に置き換わるので、メールアドレスや氏名のようなリテラルがClaudeへの入力から外れます。個人情報をプロンプトに載せたくない現場では、この性質がそのまま入力の洗浄になります。ただし-aを付けると抽象化されなくなるため、渡す前にこのオプションを使っていないか確認します。

claude -pに渡すコマンドと権限の絞り方

非対話モードのclaude -pは標準入力を読めます。ビルドログをパイプで渡して原因を説明させる例が、公式ドキュメントにもあります。集約したテキストも同じ形で渡せます。

標準入力の上限は10MBです。超えるとエラーで終了します。mysqldumpslow -t 10で絞った出力なら、まず問題になりません。それでも大きなファイルを扱うときは、パイプではなくファイルに書き出し、プロンプトでパスを指す方法に切り替えます。

CIやcronのようなスクリプト環境では、--bareを付けるのが公式の推奨です。--bareはフック、スキル、プラグイン、MCPサーバー、自動メモリ、CLAUDE.mdの自動読み込みを省きます。起動が速くなり、手元の設定に結果が左右されなくなります。代わりに、指示は--append-system-promptで明示的に足します。

cat top-total.txt top-count.txt | claude --bare -p \
  --allowedTools "Read" \
  --append-system-prompt "あなたはMySQL 8.4のチューニング担当です。\
DDLの実行やDBへの接続はしません。提案はCREATE INDEX文のテキストと\
根拠だけを出してください。" \
  "以下はmysqldumpslowの集約結果です。値はNと'S'に抽象化されています。\
合計時間と件数の両面から、インデックスで改善できそうな順に上位3件を\
選び、それぞれ必要なEXPLAINの確認項目を挙げてください。"

権限は--allowedTools "Read"にとどめ、BashもEditも許可していません。公式ドキュメントによると、Claude Code v2.1.302以降のclaude -pは、権限モードが指定されていなければManualモードで始まります。承認が要る呼び出しは、承認する仕組みがなければ拒否されます。ログ解析は「読んで答える」だけで足りるので、書き込みやコマンド実行を最初から許さないのが安全です。

誰も承認できない無人実行では、--permission-prompts noneを付けると、承認待ちになるはずの要求が拒否されます。このフラグにはv2.1.259以降が必要です。それより前のバージョンでは、未知のオプションとしてエラーになります。

結果を後続のスクリプトで扱うなら、--output-format jsonを付けます。出力にtotal_cost_usdが入るので、毎晩回すジョブの費用も追えます。ただし公式の説明どおり、この値はクライアント側の見積もりで、実際の請求とずれることがあります。

claude -pの全般的な使い方はclaude -pオプションの解説にまとめています。

EXPLAINの読み方 — type=ALLとUsing filesort

集約結果から候補を選んだら、そのクエリにEXPLAINをかけます。Nのままでは実行できないので、ログに残っている実際の値、または代表的な値に戻します。値によって実行計画が変わることがあるため、本番に近い分布のデータを持つ環境で実行します。

見る列はtype、possible_keys、key、rows、filtered、Extraです。keyは実際に選ばれたインデックスで、possible_keysは候補です。rowsは調べる行数の見積もり、filteredは条件で残る行の割合を示します。

見えるもの公式の説明読み方
type: ALL公式の説明前のテーブルの行の組み合わせごとに、全表スキャンをする読み方最初のテーブルでも好ましくなく、それ以外では大抵とても悪い
type: index公式の説明ALLに近いが、走査するのはインデックスの木読み方カバリングインデックスならExtraにUsing indexが出る
type: range公式の説明インデックスで範囲に当たる行だけ取る読み方範囲検索やIN()の典型。keyで使われた索引が分かる
Using filesort公式の説明ソート順に取り出すため、追加の処理が必要読み方ORDER BYをインデックスで済ませられていない
Using temporary公式の説明結果を保持する一時テーブルが必要読み方GROUP BYとORDER BYの列が違うときによく出る

公式は、ALLを避けるには、定数や前のテーブルの列値で行を引けるインデックスを付けるのが普通だと説明しています。つまり、type=ALLを見たら、そのWHEREやJOINの列に索引がないか、あっても使われていないかを疑います。

Using filesortという名前から、ディスク上のファイルでソートしていると受け取られがちです。公式の説明は「ソート順に行を取り出すための追加の走査が必要」というもので、ディスクに書くとは限りません。ソートが重いかどうかは、Rows_examinedの大きさや、log_slow_extraで出せるSort_merge_passesも合わせて判断します。

会話

type=ALLが出たら、すぐ索引を足せばよいのか

  1. 後輩

    EXPLAINでtype=ALLが出ました。WHERE句の列にインデックスを足せば終わりですか。

  2. 先輩

    足す前にrowsを見ます。テーブルが数百行なら全表スキャンのほうが速いこともあります。数十万行のテーブルで、返す行が数件なら、索引で効果が出やすい形です。

  3. 後輩

    ALLの意味だけで判断してはいけないんですね。

  4. 先輩

    typeは入口です。rowsとfilteredを合わせて読んで、Claudeにも同じ観点で説明させます。

ClaudeにEXPLAINを読ませるときは、出力の表を丸ごと貼り、「typeとExtraの値をそれぞれ説明してから、足すインデックス案を1つに絞って」と頼みます。複数案を一度に出させると、効果を比べる手間が増えます。

インデックス案を採用する前に確かめること

Claudeが返すCREATE INDEXは、あくまで候補です。次の順に確かめてから本番に入れます。

手順

提案を本番に入れるまでの流れ

  1. 1

    複数列の並びを確認する

    等価条件の列を先、ORDER BYの列を後に並べる形が一般的な考え方です。提案の列順が、実際のWHEREとORDER BYに沿っているかを見ます。

  2. 2

    検証用の環境で作る

    本番と近い件数のコピーか、レプリカでCREATE INDEXを実行します。本番に直接流す前提では提案を受け取りません。

  3. 3

    同じクエリでEXPLAINを撮り直す

    typeがALLからrefやrangeに変わったか、keyに新しいインデックスが出たか、rowsが減ったかを比べます。

  4. 4

    Rows_examinedで実測する

    スロークエリログでRows_examinedが下がり、Query_timeが縮んだかを、同じ負荷で確認します。

mysqldumpslowは値を抽象化するので、Claudeの提案は値の偏りを知らないまま出ています。偏りの大きい列では、EXPLAINで選ばれるインデックスが期待と違うことがあります。実測で確かめる工程を省かない理由はここにあります。

提案が複数あるときは、1つずつ検証します。索引を一度に何本も足すと、どれが効いたのか分からなくなり、書き込みの負荷も上がります。

つまずきやすい点

落とし穴

log_queries_not_using_indexesを入れた途端にログが膨れる

インデックスを使わないクエリまで記録する設定です。件数の多い環境では、数分でファイルが大きくなることがあります。log_throttle_queries_not_using_indexesで1分あたりの上限を決めると、抑えられます。上限を超えた分は、抑制された件数と合計時間の要約として記録されます。

落とし穴

管理系の文は分析対象に入らない

ALTER TABLEやCREATE INDEXは、log_slow_admin_statementsを有効にしない限りログに出ません。インデックス追加の所要時間を測りたいときは、この変数を先に有効にします。

もう1つの落とし穴が、ログの順序です。サーバーは、文を実行してロックを解放した後でログに書きます。ログ上の並びが実行順と一致するとは限りません。時系列で原因を追うときは、Thread_id(log_slow_extraで出力)やタイムスタンプで並べ直します。

最後に、PostgreSQLをお使いなら、拡張を使った索引提案の流れがPostgres MCP Proの使い方にあります。MySQLでは、ここで見たようにログとEXPLAINを素材にして、Claude Codeを「読む担当」に徹させる構成が取りやすい形です。

まとめ

入力を絞るほど、提案は具体的になります。ログ全体ではなく、mysqldumpslowの合計時間順と件数順の上位にEXPLAINを添えて渡します。権限はReadだけにとどめ、DDLの実行は人が検証環境で行います。type=ALLはrowsと合わせて読み、Using filesortはソートの追加走査という意味でとらえます。最後は必ず、EXPLAINの再実行とRows_examinedの実測で効果を確かめます。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/headless
  2. dev.mysql.com/doc/refman/8.4/en/slow-query-log.html
  3. dev.mysql.com/doc/refman/8.4/en/mysqldumpslow.html
  4. dev.mysql.com/doc/refman/8.4/en/explain-output.html
この記事を共有:XはてブLinkedIn