sql-plan-audit について
sql-plan-audit は、sqlc 形式のクエリカタログにある SQLite のクエリプランを確認するスキルです。安定したレポートとベースライン比較により、コードレビューや CI でのプラン劣化検出を支援します。
主な機能
- 01SQLite EXPLAIN レポートの生成
- 02JSON ベースラインとのプラン比較
- 03CI での劣化検出
- 04全表スキャンと一時ソートの表示
- 05sqlc.arg と sqlc.slice の処理
活用シーン
- 01スキーマやクエリ変更後のプラン確認
- 02新しいインデックスの利用確認
- 03クエリカタログの定期監査
- 04ベースラインからの劣化時に CI を失敗させる
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mizchi/ skills --skill sql-plan-auditインストール後、Claude Code で/sql-plan-auditを入力して読み込まれたか確認します。
同梱ファイルREADME.mdscripts/explain-runner.mjs
詳細
SQL プラン監査
sqlc 形式のクエリカタログ(または -- name: X :type マーカーを含むファイル)があり、コードレビューでクエリプランを確認できる状態に保ちたいプロジェクトで使用します。
理由
sqlc はコード生成時に SQL 構文を検証しますが、実行プランは監視しません。カラム名の変更、インデックスの削除、あるいは WHERE 句への小さな条件追加によって、クエリが SEARCH USING INDEX から SCAN TABLE に予告なく変わることがあります。Cloudflare D1 / SQLite では EXPLAIN ANALYZE が使えないため、プランナーの出力が唯一の静的な手がかりです。このスキルでは、その出力をレビュー可能な成果物として固定します。
呼び出すタイミング
- PR でスキーマまたはクエリカタログが変更されたとき。
- 新しいインデックスを追加し、クエリで使われることを確認したいとき。
- 既存のクエリカタログを四半期ごとに監査するとき。
ワークフロー
- スキーマファイルとクエリカタログファイルを特定します。一般的な構成は次のとおりです。
<project>/db/schema.sql <project>/db/queries.sql (or db/sqlite/query.sql for sqlc projects) - ランナーを実行します。
node scripts/explain-runner.mjs \ --schema your-project/db/schema.sql \ --queries your-project/db/queries.sql \ --out your-project/.linters/query-plans.txt - 出力をコミットします。テキスト形式は実行間で行が安定しており、差分からプランの変更を確認できます。
- CI でチェックするには、JSON に再生成し、コミット済みのベースラインと比較します。
node scripts/explain-runner.mjs \ --schema your-project/db/schema.sql \ --queries your-project/db/queries.sql \ --baseline your-project/.linters/query-plans.json \ --format json --fail-on regress - 意図的な性能低下(例:インデックスを意図して廃止した場合)を取り込む際は、同じ PR でベースラインを再生成します。
成功時と失敗時の出力
ランナーは成功時に意図的に何も出力しません。CI に組み込む際は、出力先を把握しておくことが重要です。
| 実行方法 | stdout | stderr | 終了コード |
|---|---|---|---|
--out <path>(再生成) | なし | Node の node:sqlite に関する実験的機能の警告のみ | 0 |
--out なし(再生成) | テキスト/JSON の完全なレポート | 実験的機能の警告のみ | 0 |
--baseline <path> --fail-on regress(CI チェック)、問題なし | テキスト/JSON の完全なレポート(または --out が設定されていれば何もなし) | 実験的機能の警告のみ | 0 |
--baseline <path> --fail-on regress(CI チェック)、性能低下あり | テキスト/JSON の完全なレポート | クエリごとの性能低下の詳細(変更前と変更後のプラン) | 1 |
| いずれかのパスで内部エラー(例:スキーマの読み込みに失敗) | なし、または一部のみ | Node のスタックトレース | 1 |
次のように理解してください。合否の診断情報は stderr に出力されます。CI では stderr を明示的に取得してください(2>&1、2>artifact.log、またはストリームを分けて取得)。stdout だけをパーサーに渡すと、すべての性能低下メッセージが失われます。再生成の実行で「何も出力されない」場合は、$? と ls <out path> を確認してください。これはドキュメントに記載された正常な動作です。
プレースホルダーの処理方法
sqlc.arg('x') と sqlc.slice('x') は、EXPLAIN の実行前に NULL に書き換えられます。プランはバインド値ではなく SQL の形状に依存するため、これは安全です。位置指定プレースホルダーの ? はそのまま残されます。SQLite は EXPLAIN 内でこれを受け付けます。
重大度マーカー
!SCAN — 全テーブルスキャンです。通常、インデックスがない場合、またはクエリが意図的にすべての行を処理する場合に発生します。?TEMP B-TREE —USE TEMP B-TREE FOR ORDER BY / GROUP BY / DISTINCT。並べ替えの対象をカバーするインデックスがないため、メモリ内でソートが行われます。SEARCH — インデックスが使われています。正常です。
小さなテーブル(例:行数が 1k 未満の users)での SCAN は、多くの場合問題ありません。このマーカーは判定ではなく、注意を促すものです。
CI への統合
JSON 形式を実行し、性能低下があれば失敗するステップを追加します。一般的な justfile の例:
sql-plan-audit:
node scripts/explain-runner.mjs \
--schema db/schema.sql \
--queries db/queries.sql \
--out .linters/query-plans.txt
node scripts/explain-runner.mjs \
--schema db/schema.sql \
--queries db/queries.sql \
--format json --out .linters/query-plans.json
sql-plan-audit-check:
node scripts/explain-runner.mjs \
--schema db/schema.sql \
--queries db/queries.sql \
--baseline .linters/query-plans.json \
--format json --fail-on regress
プッシュ前(pkfire / lefthook / pre-commit)に実行するのが適切です。コミットのたびに実行すると、時間がかかり、不要な出力も増えます。
制限事項
- SQLite の EXPLAIN は推定行数を報告しないため、ランナーは「1,000 万行に対する悪い SCAN」と「50 行に対する問題のない SCAN」を順位付けできません。手動レビューと併用してください。
- FTS5 仮想テーブルは
SCAN VIRTUAL TABLEと表示されます。これは情報として扱われ、警告対象にはなりません。 datetime('now')のような関数はプランの生成時に評価されます。インメモリ DB には同じスキーマがありますが行が存在しないため、副作用(書き込み)は実行されません。- サブクエリや CTE によってプランに追加の行が出力される場合がありますが、差分では一貫して扱われます。
エンジンの拡張性
ランナーは SQLite 専用です。Postgres / MySQL に対応するには、node:sqlite ドライバーと EXPLAIN 構文を差し替えます。パーサー、ベースラインとの差分処理、重大度の分類はエンジンに依存しません。ここでは実装していません。必要になったら、並列のスクリプトを追加してください。
要件
- Node 22 以降(組み込みの
node:sqliteモジュールを使用します)。 - sqlc スタイルのクエリカタログ(
-- name: X :typeマーカー)。parseQueryCatalogをランナーで調整すれば、ほかの名前付きクエリ形式にも対応できます。
ファイル
scripts/explain-runner.mjs— CLI エントリーポイント。依存関係はありません。