sql-schema-audit について
sql-schema-auditは、SQLiteのインデックス網羅性とN+1クエリの可能性を定期的に調べるDBA向けスキルです。クエリ計画のスキャンを分析し、確認が必要なインデックス候補やループを人手レビュー用に示します。
主な機能
- 01クエリ計画のSCANを分類
- 02未使用の可能性があるインデックスを列挙
- 03外部キーのカスケードに使うインデックスを識別
- 04sqlc関数を呼ぶループを検出
- 05スキャン条件の調整
活用シーン
- 01スキーマ変更後のインデックス確認
- 02リリース前のインデックス候補の点検
- 03DBループ内のN+1クエリ候補の調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mizchi/ skills --skill sql-schema-auditインストール後、Claude Code で/sql-schema-auditを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
README.mdscripts/index-coverage.mjsn-plus-one.mjs詳細
SQL スキーマ監査
DBA が繰り返し行う次の 2 つのレビュー作業に使用します。
- インデックス対応が不足している箇所は? すべてのクエリプランを確認し、各
SCAN <table>を、既知の意図的なケース(カバリングインデックスを作成できない)か、未対応箇所(テーブルにインデックスがまったくない)に分類します。 - コードに N+1 が発生している可能性は? sqlc が生成した関数を呼び出す
forループを見つけます。これはレビューの補助であり、厳格な失敗判定ではありません。バッチ挿入が正当なケースもあるためです。
index-coverage.mjs
# `scripts/` is relative to THIS skill's directory; from elsewhere use the
# absolute path. `--out` is OPTIONAL — omit it and the report prints to stdout.
# The printed report IS the deliverable; the file is just a persisted copy.
node scripts/index-coverage.mjs \
--schema your-project/db/schema.sql \
--queries your-project/db/queries.sql \
[--out your-project/.linters/index-coverage.txt]
出力セクション:
- クエリごとの SCAN 分類:
SCANステップを生成する各クエリと、その SCAN の理由を表示します。FIXマーカーは、テーブルにインデックスがまったくないことを示します(すぐに対応が必要です)。マーカーがない場合、テーブルにはインデックスがありますが、プランナーがスキャンを選択しています。通常は問題ありません(テーブルが小さい、WHERE の形が通常と異なるなど)。 - 削除候補:カタログ内のクエリで
SEARCH USING INDEXを通じて参照されておらず、かつ FK CASCADE の参照元カラムをカバーしていないインデックスです。 - FK カスケードを支えるインデックス:SELECT では使われていないものの、先頭のカラムが外部キーの
fromリストと一致するインデックスです。SQLite は FK カラムのインデックスを自動作成しません。明示的なインデックスがなければ、ON DELETE CASCADEは子テーブルを全件スキャンします。これらは「未使用」に見えますが、削除のパフォーマンスを支えています。
FK を考慮する手順が重要です。owner_user_id 外部キーが多数ある一般的なスキーマでは、「未使用」インデックスの大半は、実際にはカスケード削除を支えています。これらを削除すると、気づかないうちに削除パフォーマンスが低下します。
候補を削除する場合
削除する前に、次の点を照合してください。
- この監査が調べるのは sqlc が管理するクエリのみです。インライン SQL(FTS5 の
MATCH、動的なLIKE、ベクトル検索など)は監査対象外です。削除する前に、インデックス名またはカラムの組み合わせをコードベース内で grep してください。 - 静的な EXPLAIN では再現されないクエリの形では、プランナーが実行時に動的にそのインデックスを選択することがあります(たとえば、バインド値の分布が変わり、選択されるインデックスが変化する場合)。削除候補とは「分析対象のカタログでは、SELECT が一度も選択しない」という意味です。これは必要条件であり、十分条件ではありません。
- 論理的に上位集合のインデックスと「重複」するインデックスは、通常削除できます。プランナーは、より長いインデックスを使うよう切り替えます。削除後に
sql-plan-auditで検証し、クエリが SCAN に戻っていないことを確認してください。
n-plus-one.mjs
node scripts/n-plus-one.mjs your-project/src
for ループ内の 12 行以内で sqlc が生成した関数を呼び出している箇所を、正規表現でスキャンします。次のオプションで調整できます。
- 他のバインディング形式(Rust の
state.db.、Go のq.など)に一致させるには、--callee-prefix @db.,db.を指定します。 - 先読み範囲を広げるには、
--window 20を指定します。
このスクリプトは常に 0 を返します。N+1 の候補には人によるレビューが必要です。CI のゲートではなく、レビューの補助として使用してください。
型付けがしっかりしたコードベースで見つかる候補の大半は、正当なバッチ挿入です(for file in files 内の create_skill_file、for tag in tags 内の add_skill_tag など)。このレポートで検出したいのは、実際に問題となる SELECT のループ内実行です。
実行するタイミング
- スキーマを変更した後(インデックスの追加または削除)。
- リリース前に、「未使用のものは何か」を一度確認するとき。
- DB にアクセスする
forループを含む機能を追加したとき。N+1 レポートを目視確認してください。
対象外
- コードと本番環境のスキーマの不一致:稼働中の DB スキーマを読み取り、
schema.sqlと比較する必要があります。ここでは対象外です。 - CHECK / NOT NULL 監査:schemalint などが必要です。おそらく今後追加する価値のあるスキルです。
- EXPLAIN ANALYZE / 実際の行数:SQLite では静的に取得できません。
エンジンの拡張性
PRAGMA ベースのイントロスペクション(PRAGMA index_list、PRAGMA index_info、PRAGMA foreign_key_list)は SQLite 固有です。Postgres では、pg_indexes、pg_stat_user_indexes(実際の使用回数を取得でき、EXPLAIN ベースのヒューリスティックよりはるかに正確です)、information_schema.referential_constraints をクエリします。MySQL では、information_schema.STATISTICS と information_schema.KEY_COLUMN_USAGE を使用します。
N+1 検出器はエンジンに依存しませんが、--callee-prefix のヒューリスティックはコード生成の規約(sqlc / sqlx / typeorm / prisma)に依存します。
要件
- Node 22 以降(組み込みの
node:sqliteモジュールを使用します)。 - sqlc スタイルのクエリカタログ、または互換形式。
ファイル
scripts/index-coverage.mjs— クエリごとの SCAN の原因特定、削除候補、FK カスケードに重要なリスト。scripts/n-plus-one.mjs—forループ内の sqlc 呼び出し検出器。