SQL脚本修复·先体检,再给可评审的修复稿
线上事故里,SQL 脚本占了一大类:UPDATE/DELETE 忘了写 WHERE 直接全表;
DROP TABLE 没加 IF EXISTS,重跑一次就炸;逗号连接漏了连接条件跑出笛卡尔积;
WHERE id = '1001' 触发隐式类型转换;口令硬编码在脚本里随代码进仓库。
本工具做纯静态检查,并把能确定安全的写法直接改掉,危险的写法用 [TODO] 注释标出来。
不连接数据库、不执行任何 SQL——只读文本,先查再跑。
执行原则
- 只读不连库。 只做静态文本规则检查:不连接数据库、不执行 SQL、不读表结构与执行计划;不要声称查过真实数据量或真实索引。
- 能安全改才改。 只自动改语义确定的写法(补
IF EXISTS、=NULL 改IS NULL、去数值列多余引号、凭据掩码);不要给UPDATE/DELETE补WHERE——补哪一行必须由业务判断。 - 危险项只标注。 其余规则一律在语句上方加
-- [TODO Qx]注释与建议,不擅自改语义;不要跳过人工确认就让人直接执行修复稿。 - 凭据只掩码。 疑似口令/密钥替换为
***REDACTED***;不要在输出或对话里回写明文,并提示轮换——原始脚本仍含明文。 - 不覆盖原脚本。
--fix只写你指定的目标文件;不要把--fix指向输入文件本身(会直接覆盖原稿)。
何时使用
CRITICAL: 只要用户提出「这段建表 / 迁移 / 订正脚本能不能直接执行 / 有没有全表更新或误删的风险 / 帮我审一下这段 SQL 有没有问题」,就必须使用本 skill,即使看起来可以用别的方式解决。
- 建表/迁移/数据订正脚本上线前,先过一次检查。
- Code Review 中拿到一段 SQL 脚本,想快速定位高风险写法。
- 排查「为什么这条查询这么慢」:先看有没有前导通配、函数包列、隐式类型转换。
- 用户给出
.sql文件,问「这段脚本能不能直接执行」。
怎么用
# 1) 体检 + 生成修复稿(推荐)
python3 scripts/sql_lint.py 建表脚本.sql --fix 建表脚本-修复版.sql
# 2) 直接打印修复结果
python3 scripts/sql_lint.py 建表脚本.sql --fix -
# 3) 只看问题清单
python3 scripts/sql_lint.py 建表脚本.sql
python3 scripts/sql_lint.py 建表脚本.sql --json
# 4) 看内置演示
python3 scripts/sql_lint.py --sample --fix -
--fix 自动修什么
| 规则 | 处理方式 |
|---|---|
Q4 DROP 无保护 |
自动补 IF EXISTS |
Q7 数值列与字符串比较 |
id = '1001' → id = 1001 |
Q13 = NULL / <> NULL |
改为 IS NULL / IS NOT NULL |
Q15 硬编码凭据 |
替换为 '***REDACTED***',并提示轮换 |
Q1/Q2/Q3/Q5/Q6/Q8/Q9/Q10/Q11/Q12/Q14 |
不擅自改语义,在对应语句上方加 -- [TODO Qx] 和修复建议 |
修复稿开头会写明:标 [TODO] 的地方必须人工确认后再执行。
检查项与规则码
| 规则码 | 含义 | 严重度 | 定位 |
|---|---|---|---|
Q1 |
使用 SELECT * |
中 | 语句 |
Q2 |
UPDATE 缺 WHERE(全表更新) |
高 | 语句 |
Q3 |
DELETE 缺 WHERE(全表删除) |
高 | 语句 |
Q4 |
DROP TABLE/DATABASE 无 IF EXISTS 保护 |
高 | 语句 |
Q5 |
逗号连接且 WHERE 无连接条件 |
中 | 语句 |
Q6 |
多表 FROM 但无 JOIN/连接条件(笛卡尔积) |
高 | 语句 |
Q7 |
字符串常量与列比较(隐式类型转换) | 中 | 语句 |
Q8 |
LIKE '%...' 前导通配(索引失效) |
低 | 语句 |
Q9 |
WHERE 中对列使用函数(索引失效) |
中 | 语句 |
Q10 |
查询无 LIMIT/TOP |
低 | 语句 |
Q11 |
使用 WITH (NOLOCK) 脏读提示 |
低 | 语句 |
Q12 |
CREATE TABLE 无 PRIMARY KEY |
中 | 语句 |
Q13 |
= NULL / <> NULL(应改 IS [NOT] NULL) |
中 | 语句 |
Q14 |
NOT IN (子查询)(NULL 陷阱) |
低 | 语句 |
Q15 |
疑似硬编码口令/密钥字面量 | 中 | 语句 |
EMPTY_SQL |
文件为空或仅含空白 | 高 | 全文 |
NO_STATEMENT |
未识别出任何 SQL 语句(可能全是注释) | 高 | 全文 |
输出示例(节选)
-- 由 sql-lint --fix 生成:已完成确定性修复;标 [TODO] 的地方必须人工确认后再执行。
DROP TABLE IF EXISTS tmp_orders;
-- [TODO Q2] UPDATE 缺 WHERE(全表更新):UPDATE 必须带 WHERE;确需全表更新请拆成显式脚本并加备份/事务与评审
UPDATE orders SET status = 2;
-- [TODO Q14] NOT IN 子查询(NULL 陷阱):改用 NOT EXISTS 或排除 NULL
SELECT id FROM users WHERE id NOT IN (SELECT user_id FROM blacklist);
设计边界
- 纯标准库 Python(3.12+),只读文本,无网络请求,无
subprocess/eval/exec。 - 只做静态文本规则检查,绝不连接数据库、绝不执行任何 SQL;不解析执行计划、不评估真实数据分布。
- 规则基于文本模式(正则),会存在少量误报/漏报;
--fix不擅自给UPDATE/DELETE补WHERE,
因为「补哪一行」必须由业务判断。 - 支持的方言差异不做区分(MySQL / PostgreSQL / SQL Server / Oracle 通用写法)。
免责声明
本技能给出的是静态文本层面的风险提示与修复建议,不是执行许可,也不构成安全审计结论:
--fix 只做规则内替换,不保证改写后的语义符合你的业务意图;
未人工复核直接执行造成的全表更新、误删或数据损失,不由本工具承担。
Q15 只是疑似识别,命中不等于凭据一定有效或已泄露,是否轮换由你判断。
安全提醒
- 若体检出现
Q15,请立即:① 从脚本中删除该口令/密钥并改为引用环境变量或密钥管理服务;
② 轮换已泄露的凭据。 Q2/Q3/Q4/Q6命中时,先确认再执行:全表更新/删除、无保护 DROP、笛卡尔积都可能造成不可逆的数据损失。
如果这个 skill 帮你在上线前排掉一次事故,欢迎在 SkillHub 页面点「收藏」,下次审 SQL 时直接调用。