关于 sql-schema-audit
sql-schema-audit 面向 DBA 的重复性审查任务,检查 SQLite 索引覆盖情况和潜在 N+1 查询。脚本会分析查询计划中的扫描、列出待核查的索引,并标记循环供人工复审。
主要功能
- 01归因查询计划中的 SCAN 步骤
- 02列出可能未使用的索引
- 03识别用于外键级联的索引
- 04标记调用 sqlc 函数的循环
- 05支持可配置的扫描模式
适用场景
- 01架构变更后检查索引
- 02发布前核查待处理的索引
- 03检查数据库循环中的潜在 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 Schema Audit
将其用于两项周期性的 DBA 审查任务:
- 哪些地方缺少索引? 遍历每个查询计划,并将每个
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、向量查询)不会纳入审计。删除前,请在代码库中搜索索引名称或其列组合。 - 对于静态 EXPLAIN 无法复现的查询形状,规划器可能会动态选择该索引(例如,不同的绑定值分布会改变所选索引)。删除候选的含义是“在已分析的目录中没有 SELECT 选择它”——这是必要条件,但不是充分条件。
- 逻辑上“被”超集索引覆盖的索引通常可以删除——规划器会改用更长的索引。删除后,请使用
sql-plan-audit验证,确保没有查询退化为 SCAN。
n-plus-one.mjs
node scripts/n-plus-one.mjs your-project/src
基于正则表达式扫描 12 行范围内调用 sqlc 生成函数的 for 循环。可调整参数:
--callee-prefix @db.,db.:匹配其他绑定风格(Rust 的state.db.、Go 的q.等)。--window 20:扩大向前查找范围。
该脚本始终返回 0——N+1 候选需要人工审查。请将其用作审查辅助工具,而不是 CI 门禁。
在类型完善的代码库中,大多数候选都是合理的批量插入(在 for file in files 中调用 create_skill_file,在 for tag in tags 中调用 add_skill_tag)。本报告旨在捕捉的,正是那种在循环中执行 SELECT 的真实问题。
何时调用
- 架构变更后(添加或删除索引)。
- 发布前:一次性检查“哪些索引未使用?”。
- 功能新增了访问数据库的
for循环时——查看 N+1 报告。
不在范围内
- 代码与生产环境之间的架构漂移:需要读取线上数据库架构,并与
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 调用检测器。