6.8 KiB
6.8 KiB
name, description
| name | description |
|---|---|
| sql-review | 对执行前生成好的 PySpark SQL 字符串做静态评审时使用。触发词包括"review this SQL"、"SQL 评审"、"静态审查"、"risk level"、"check join cardinality"、"数据膨胀"、"重复聚合"、"分区裁剪"。消费 SQL 字符串及其产出的 SQL Context,产出按风险分级的评审结果(HIGH / MEDIUM / LOW)以及具体修复建议。**不修改 SQL** — 只报告发现。 |
SQL Review(SQL 评审)
概述(Overview)
对一条生成好的 PySpark SQL 字符串做执行前的静态评审。评审者不改写 SQL — 它只输出发现项、严重程度和建议修复。由 SQL 的作者决定采纳哪些。
核心原则:对照 SQL Context 评审,而不是凭直觉。 SQL 中引用的每一个字段、alias、join key 都必须存在于产出它的 SQL Context。如果不存在,这就是一项 HIGH 风险(SQL 绕过了契约)。
何时使用(When to Use)
使用场景:
- 一条 SQL 字符串已经从
sql_context生成出来,即将执行 - 用户粘贴一条 SQL 字符串,要求评审、优化或风险评估
- 代码改动为 PySpark 脚本新增了
spark.sql(...)调用
不要使用场景:
- 这条 SQL 不是 Spark SQL(例如纯 Postgres、HiveQL 在 Spark 之外)— 至少要把 Spark 特有的检查替换成对应引擎的等价检查,并标注出来
- 是一次性、不会被复用的交互查询 — 用户可以主动选择跳过
输入(Inputs)
- 要评审的 SQL 字符串
- 产出这条 SQL 的 SQL Context(如果缺失,需要显式声明"该 SQL 是不可信 / 临时拼凑的")
如果 SQL Context 缺失,抛出一项 HIGH 风险:"SQL 没有对应的 SQL Context;下游 skill 无法保证字段 / join 的正确性。" 然后继续做结构性检查。
严重程度模型(Severity Model)
| 等级 | 含义 | 处置 |
|---|---|---|
HIGH |
会导致数字错误、执行失败或违反策略 | 阻断执行,必须修。 |
MEDIUM |
大概率对,但低效、脆弱或大规模场景有风险 | 上线前修,开发环境可放过。 |
LOW |
风格 / 最佳实践小瑕疵 | 可选修。 |
按以下顺序输出发现项:HIGH → MEDIUM → LOW。任一 HIGH 让整体评审结论为 FAIL。
工作流(Workflow)
digraph sql_review {
"解析 SQL(逻辑层面,不只是正则)" [shape=box];
"对照 SQL Context 交叉校验 alias、字段、join key" [shape=box];
"检测多语句 / 禁用 verb(防护栏)" [shape=box];
"分析 join 基数与膨胀风险" [shape=box];
"检测同一度量的重复聚合" [shape=box];
"检测全表扫描与缺失分区过滤" [shape=box];
"校验 GROUP BY 与 SELECT 一致" [shape=box];
"检查 join key 与度量的空值处理" [shape=box];
"检查字段类型与运算的匹配" [shape=box];
"汇总成评审结论" [shape=box];
"解析 SQL(逻辑层面,不只是正则)" -> "对照 SQL Context 交叉校验 alias、字段、join key";
"对照 SQL Context 交叉校验 alias、字段、join key" -> "检测多语句 / 禁用 verb(防护栏)";
"检测多语句 / 禁用 verb(防护栏)" -> "分析 join 基数与膨胀风险";
"分析 join 基数与膨胀风险" -> "检测同一度量的重复聚合";
"检测同一度量的重复聚合" -> "检测全表扫描与缺失分区过滤";
"检测全表扫描与缺失分区过滤" -> "校验 GROUP BY 与 SELECT 一致";
"校验 GROUP BY 与 SELECT 一致" -> "检查 join key 与度量的空值处理";
"检查 join key 与度量的空值处理" -> "检查字段类型与运算的匹配";
"检查字段类型与运算的匹配" -> "汇总成评审结论";
}
检查项(Checks,全部执行)
- 引用完整性(Reference integrity) — SQL 中的每个
<alias>.<column>都必须存在于 SQL Context。不匹配即HIGH - Join key 匹配 — 每个
ON谓词都必须对得上 SQL Context 中已固化的 join。凭空发明的 join 算HIGH - 禁用 verb / 多语句 — 交给
pyspark-sql-guardrails。任何 DDL/DML/维护 verb,或任何;堆叠的语句,均为HIGH - Join 基数风险 — 对每个 join,如果 SQL Context 标记基数为
1:N或N:N且没有去重,分别抛MEDIUM(1:N)或HIGH(N:N) - 重复聚合 — 同一度量被聚合两次(例如子查询里一次、外层再一次)且非有意重新聚合,抛
HIGH - 分区 / 时间过滤 — 对任何有已知时间字段的事实表,缺少对该字段的
WHERE过滤即为HIGH(全表扫描风险) - GROUP BY 完整性 — SELECT 中每个未聚合的列都必须出现在
GROUP BY中。不匹配为HIGH - 空值处理 — 可空 join key 上做
INNER JOIN会静默丢行(MEDIUM);可空度量未用COALESCE聚合,可能产出NULL进而破坏下游WHERE ... > 0(MEDIUM) - 类型不匹配 — 字符串列未做 cast 就参与数值聚合(
MEDIUM);日期字符串和TIMESTAMP字面量比较(MEDIUM) - Spark 最佳实践 —
COUNT(*)vsCOUNT(1)属于风格(LOW);生产环境用SELECT *算MEDIUM;数据倾斜 join 缺DISTRIBUTE BY/CLUSTER BY算LOW到MEDIUM
输出契约(Output Contract)
sql_review:
verdict: <PASS | FAIL>
highest_severity: <HIGH | MEDIUM | LOW>
summary: "<一句话总结>"
findings:
- id: F1
severity: HIGH
check: reference_integrity
location: "<行/列号或 SQL 片段>"
message: "<错在哪>"
fix: "<具体的建议修改>"
- id: F2
severity: MEDIUM
check: join_cardinality
location: "JOIN customer_info"
message: "1:N join without pre-aggregation; risk of row inflation"
fix: "Aggregate loan_order by customer_id before joining"
notes:
- "SQL Context provided: yes (sql_context.aliases = [...])"
- "Reviewed by: sql-review v1"
禁止行为(Forbidden Behaviors)
| 自我说服 | 现实 |
|---|---|
| "SQL 看起来没问题,不用对一遍 context" | 字段可能"看起来对"但源表里根本没有,必须做交叉校验。 |
| "我顺手在评审时把 SQL 修了" | 评审只报告,修是作者的事。职责分离。 |
| "没给 SQL Context,那字段检查就跳过" | 缺 context 本身就是 HIGH,绝不能悄悄跳过。 |
| "只是开发查询,分区检查跳过" | 这一项成本极低,跳过就会留下静默的全表扫描。 |
| "1:N join 是常态,没风险" | 在保留粒度的前提下 1:N 是 OK 的,但评审必须验证,不能假设。 |
| "用正则就够了,能查 GROUP BY" | GROUP BY 成员关系是结构性的,要用解析器,不要用正则。 |
完成判定(Completion Criteria)
评审完成的条件:
- 全部 10 项检查都已评估
- 每一条
HIGH风险都有fix - 当且仅当
highest_severity为MEDIUM或LOW时,verdict才为PASS - 输出的 YAML 可直接贴到 PR 评论,无需修改