← 返回 Skill 列表
extension
分类: 开发与工程API Key 暂未确认

SQL 审查

审查 MySQL / OceanBase / seekdb 的 SQL 与执行计划:19 条静态规则定位整表更新、笛卡尔 JOIN、LIKE 前导通配符、列上套函数、NOT IN 子查询、深分页、建表无主键、隐式类型转换等风险;支持 MySQL 表格、EXPLAIN ... \G 垂直格式与 OceanBase 算子树三种计划解析;输出按阻断/高危/建议排序的 Markdown 或 JSON 报告,每条附改写建议。纯 Python 标准库实现,不连数据库、不联网,可用 --fail-on 接入 CI。

person作者: WeMe0721hubModelScope

SQL 审查

把"这条 SQL 能不能上线"变成一份可执行的问题清单:先静态检查语法层面的风险,再结合执行计划判断真实瓶颈,最后给出改写后的语句。

适用于 MySQL、OceanBase、seekdb 等 MySQL 兼容数据库。核心脚本只依赖 Python 标准库,不连接数据库、不联网。

什么时候用

  • 用户贴出 SQL,问"有问题吗 / 能上线吗 / 为什么慢"
  • 需要 review 迁移脚本、上线脚本、ORM 生成的语句
  • 拿到 EXPLAIN 输出,需要判断瓶颈在哪一步
  • 建表、加索引前,想确认设计是否合理
  • Code Review 中发现可疑查询,需要给出具体的改写

什么时候不用

  • 必须依赖真实数据分布才能判断的问题:先让用户提供 EXPLAIN 与表结构,再下结论
  • 性能问题来自锁等待、连接池、网络或缓存,而不是 SQL 本身
  • 非 MySQL 方言(PostgreSQL / Oracle / ClickHouse):部分规则仍适用,但优化器行为不同,结论需人工复核
  • 需要实际执行才能验证的语义问题(如存储过程逻辑)

快速开始

# 1. 静态审查,输出 Markdown 报告
python scripts/sql_review.py path/to/query.sql

# 2. 同时分析执行计划(MySQL 表格 / 垂直格式、OceanBase 算子树都支持)
python scripts/sql_review.py query.sql --explain explain.txt

# 3. 输出 JSON,接入流水线;发现 major 及以上返回非零
python scripts/sql_review.py query.sql --format json --fail-on major

工作流

第 1 步:收集三样东西

  1. SQL 原文:优先用慢日志或应用日志里的最终语句,而不是拼接前的模板。
  2. 表结构与索引:SHOW CREATE TABLE,重点看索引列顺序。
  3. 数据量级:几千行和上千万行的判断完全不同。

缺少第 2、3 项时可以做静态初筛,但结论要标注"待确认"。

第 2 步:机器初筛

跑 sql_review.py,把 blocker 与 major 全部过一遍;minor、nit 按场景取舍。脚本输出是线索,不是判决。

第 3 步:计划验证

有 EXPLAIN 就用 --explain 一起分析;没有就建议用户对目标库执行 EXPLAIN。静态规则与执行计划冲突时,以执行计划为准。

第 4 步:人工判定

逐条确认三件事:是不是误报、是否在本次变更范围内、修复成本与收益是否匹配。误报的常见原因见 references/rules.md 的"已知边界"。

第 5 步:输出

按"阻断 → 高危 → 建议"排序。每条都要给出:位置、影响、改写后的 SQL。只给结论不给语句,等于没审。

严重级别怎么用

| 级别 | 含义 | 处理 | | --- | --- | --- | | 阻断 | 可能造成数据事故或不可逆影响 | 拒绝上线,先改 | | 高危 | 大概率导致慢查询、锁等待或索引失效 | 本次变更内修复 | | 建议 | 有明确代价,但特定场景可接受 | 排期处理或明确接受 | | 提示 | 风格与可维护性 | 可忽略 |

高频问题的改写

1. 整表更新或删除

-- 改前
UPDATE users SET status = 0;

-- 改后:有条件、可分批
UPDATE users SET status = 0 WHERE id = 42;

确需全表更新时按主键分批,每批 1000~5000 行,批间留出间隔。

2. LIKE 前导通配符

-- 改前:索引失效
SELECT id FROM articles WHERE title LIKE '%数据库%';

-- 改后:前缀匹配可用索引
SELECT id FROM articles WHERE title LIKE '数据库%';

中缀搜索改用全文索引或搜索引擎,不要指望 B+Tree。

3. 列上套函数

-- 改前
SELECT id FROM orders WHERE DATE(created_at) = '2026-01-01';

-- 改后:范围条件
SELECT id FROM orders
WHERE created_at >= '2026-01-01' AND created_at < '2026-01-02';

4. OR 连接不同列

-- 改前:通常只能用一个索引
SELECT id FROM users WHERE status = 1 OR city = '杭州';

-- 改后:两条索引各自生效
SELECT id FROM users WHERE status = 1
UNION ALL
SELECT id FROM users WHERE city = '杭州';

结果需要去重时用 UNION,但要知道多一次去重排序的代价。

5. 深分页

-- 改前:扫描并丢弃 20 万行
SELECT id, title FROM articles ORDER BY id LIMIT 200000, 20;

-- 改后:游标分页
SELECT id, title FROM articles WHERE id > 200000 ORDER BY id LIMIT 20;

不能改游标时用延迟关联:先用覆盖索引取主键,再回表取详情。

6. NOT IN 子查询

-- 改前:子查询含 NULL 时结果为空
SELECT id FROM users WHERE id NOT IN (SELECT user_id FROM blacklist);

-- 改后
SELECT id FROM users u
WHERE NOT EXISTS (SELECT 1 FROM blacklist b WHERE b.user_id = u.id);

7. SELECT *

-- 改前
SELECT * FROM orders WHERE user_id = 42;

-- 改后:只取必要列,配合覆盖索引
SELECT id, amount, created_at FROM orders WHERE user_id = 42;

8. 隐式类型转换

-- 改前:user_id 是数值列,却与字符串比较
SELECT id FROM orders WHERE user_id = '123';

-- 改后
SELECT id FROM orders WHERE user_id = 123;

参数化查询时确认绑定类型与列类型一致,否则同样会触发转换。

输出模板

审查 <文件>,共 N 条语句,发现 X 个阻断、Y 个高危、Z 个建议。

### 阻断
1. <文件>:<行号> UPDATE 缺少 WHERE
   - 影响:整表更新,长事务并放大 binlog
   - 改写:UPDATE ... WHERE id = ?

### 高危
...

### 建议
...

### 需要你确认
- orders 表实际行数与 created_at 的区分度
- 是否已有 (user_id, created_at) 联合索引

结论先行,逐条给证据与改写;没有把握的项放进"需要你确认",不要硬下结论。

脚本参数

| 参数 | 说明 | | --- | --- | | files... | SQL 文件路径,- 表示标准输入 | | --explain FILE | 分析执行计划,可重复传入 | | --format md\|json | 输出格式,默认 Markdown | | --min-severity | 只显示不低于该级别的问题 | | --fail-on | 达到该级别返回非零状态码,便于接入 CI | | --list-rules | 打印全部规则 | | --version | 版本号 |

参考文件

| 文件 | 内容 | | --- | --- | | references/rules.md | 全部规则:触发条件、风险、改写方向、已知边界 | | references/explain.md | 怎么读 MySQL 与 OceanBase 的执行计划 | | references/index-design.md | 联合索引、覆盖索引、选择性、分页与 JOIN | | references/safety.md | 大事务、锁、DDL、批量变更的上线清单 |

边界

  • 只做静态文本分析,不连接数据库、不读取统计信息,因此所有结论都需要结合真实数据复核。
  • 不理解业务语义:小表上的全表扫描、只查三列的 SELECT * 可能完全合理。
  • 不覆盖存储过程体与动态拼接后的最终语句。
  • 规则可以扩展:在 scripts/sql_review.py 的 RULES 注册规则、在 analyze_statement 实现判定,并同步 references/rules.md 与测试。

自测

python -m unittest discover -s tests -v

测试覆盖字符串/注释遮蔽、全部语句规则、误报样例、执行计划三种格式与 CLI 行为。

<!-- OB-S:DnDptzBf_rL72T8pV5Veag -->