mcpField
S
sql-plan-audit

sql-plan-audit

github.com/mizchi数据与数据库41646 次安装1 周前更新
代码仓库

关于 sql-plan-audit

sql-plan-audit 是用于审查 sqlc 风格查询目录中 SQLite 查询计划的技能。它生成稳定的计划报告并支持基线检查,帮助团队在代码审查和 CI 中发现计划回退。

主要功能

  1. 01生成 SQLite EXPLAIN 报告
  2. 02与 JSON 基线比较计划
  3. 03在 CI 中标记回退
  4. 04标示全表扫描和临时排序树
  5. 05处理 sqlc.arg 和 sqlc.slice 占位符

适用场景

  1. 01审查架构或查询修改后的计划
  2. 02确认查询使用新建的索引
  3. 03定期审计查询目录
  4. 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。在没有 EXPLAIN ANALYZE 的 Cloudflare D1 / SQLite 上,规划器输出是唯一的静态信号。此工具会将该输出固定为可供审查的产物。

何时调用

  • PR 中更改了架构或查询目录。
  • 新增了索引,想确认查询是否会使用它。
  • 对现有查询目录进行季度审计。

工作流

  1. 确定架构文件和查询目录文件。典型布局如下:
    <project>/db/schema.sql
    <project>/db/queries.sql   (or db/sqlite/query.sql for sqlc projects)
    
  2. 运行执行器:
    node scripts/explain-runner.mjs \
      --schema your-project/db/schema.sql \
      --queries your-project/db/queries.sql \
      --out your-project/.linters/query-plans.txt
    
  3. 提交输出。文本格式在多次运行间保持稳定;差异会显示计划变更。
  4. 如需在 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
    
  5. 如果有意引入回归(例如,有意弃用了某个索引),请在同一个 PR 中重新生成基线。

成功与失败的表现

执行器在成功时会刻意保持静默。配置 CI 时,了解输出的去向很重要:

调用方式stdoutstderr退出码
--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> 进行确认;这是文档所述的正常成功情况。

执行器如何处理占位符

运行 EXPLAIN 前,sqlc.arg('x') 和 sqlc.slice('x') 会被改写为 NULL。计划取决于 SQL 结构,而非绑定值,因此这样处理是安全的。? 位置占位符会原样保留——SQLite 接受它们出现在 EXPLAIN 中。

严重性标记

  • ! SCAN — 全表扫描。通常表示缺少索引,或查询有意访问每一行。
  • ? TEMP B-TREE — USE TEMP B-TREE FOR ORDER BY / GROUP BY / DISTINCT。由于没有索引覆盖排序,排序会在内存中进行。
  • SEARCH — 命中索引,属于正常情况。

小型表上的一次 SCAN(例如少于 1k 行的 users 表)通常没有问题;此标记只是提示,并非判定结论。

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

Pre-push(pkfire / lefthook / pre-commit)是合适的检查时机——每次提交都运行会很慢,也会产生过多噪声。

局限性

  • SQLite EXPLAIN 不会报告估算行数,因此执行器无法区分“扫描 10M 行的问题”与“扫描 50 行的正常情况”。请结合人工审查。
  • FTS5 虚拟表会显示为 SCAN VIRTUAL TABLE。这会被视为信息提示,不会被标记。
  • datetime('now') 等函数会在计划生成时求值。由于内存数据库的架构相同但没有数据,因此不会执行副作用(写入)。
  • 子查询和 CTE 可能会在计划中产生额外行;差异比较会稳定地处理这些情况。

引擎扩展性

执行器专用于 SQLite。要支持 Postgres / MySQL,需要替换 node:sqlite 驱动和 EXPLAIN 语法;解析器、基线差异比较和严重性分类器与引擎无关。此处尚未实现——有需要时可添加配套脚本。

要求

  • Node 22 或更高版本(使用内置的 node:sqlite 模块)。
  • sqlc 风格的查询目录(-- name: X :type 标记)。如需支持其他命名查询格式,可调整运行器中的 parseQueryCatalog。

文件

  • scripts/explain-runner.mjs — CLI 入口,无依赖。

评论

支持 Markdown