生成式 AI 改写的 SQL 跑得更快,不代表结果正确。提速可能来自 join 类型变化——比如 inner join 悄悄丢掉了 left join 保留的行。因为显示出来的每行都有客户名,快速冒烟测试很难发现缺失。在差分检查对比新旧结果集之前,应把生成式查询改动当作补丁,而非证明。
join 变化里藏着的失败
用一个小数据夹具演示:orders 表里有一条订单,对应 customers 表中不存在的客户。这种悬空引用在遗留系统里很常见,正是 join 转换改变查询语义的地方。原查询用 LEFT JOIN 保留全部 5 行订单,其中一行 name 为 NULL;生成式改写只把 join 类型换成 INNER JOIN,因为它在 schema 保证 customer_id 都存在时读起来更好、通常也更快——但本例中该保证不成立,结果只剩 4 行。性能提升同时改变结果集,就不是优化。
合并前先做黄金结果校验
不要靠肉眼对比前几行。正确做法是建立已知正确的基线、规范化结果集,任何不匹配的候选都直接判失败。用 SQLite 脚本即可实现:构造含悬空引用的 fixture,分别执行基线与候选查询,打印候选缺失的行,结果集不一致就抛出异常。脚本直接输出消失的行,而不是让开发者盯着两张表找差异——这个差异才是调试信号。
让 fixture 贴近生产数据
只含 happy path 的测试通过意义有限。夹具应包含多种边界情况:父记录缺失的子行、无子行的父记录、重复子行、与空字符串不同的 NULL、零值/负数/最大长度等边界值、与索引顺序不同的插入顺序。即便基线与候选在这种"丑陋"夹具上返回相同结果,也只证明消除了一类静默结果集变化,并未证明正确性。
生成多个候选,统一过校验
模型返回更快查询时,最糟的下一步是因其解释听起来自信就合并。更安全的循环是要求多个改写版本,让每个候选都过同一套黄金结果检查。MonkeyCode 的免费模型访问降低了生成第二、第三个候选的成本,不必在第一个看似合理的版本就停下。这不是更信任模型,而是让模型输出足够便宜,可以当作必须通过自动化检查的假设。
替换慢查询的工作流
保存当前 fixture 的结果集,记录基线行的规范化签名;让模型生成三个候选改写而非一个;每个候选跑同一 fixture 和签名;只保留匹配基线的候选;用 EXPLAIN 或 EXPLAIN QUERY PLAN 检查匹配候选是否真的改进了执行计划。若没有候选既匹配又更快,正确输出不是合并补丁,而是列出模型下次应遵守的约束清单。
免费服务端方案的适用场景
笔记本能跑小 fixture,但某些查询 bug 只在大快照下出现,而大快照无法复制到每台开发机。此时可把黄金结果集放到小型对比端点后,让每个候选提交输出换取通过/失败响应。端点做两件事:报告候选行集是否等于基线,返回缺失或多余行的 diff——这个 diff 比模型的解释更有用,因为它来自数据。不要把真实客户数据发给模型或公共服务器,应生成保留相同 join、null、基数陷阱的 fixture,在可控环境里跑完整隐私敏感对比。
方法的局限
差分测试能捕获语义回归,但不能证明查询正确,只证明候选匹配所选基线。SQLite 适合本地 fixture,但 SQL 语义和优化器行为与 PostgreSQL、MySQL、SQL Server、Oracle 不同。黄金基线本身可能错误或过期;查询可能匹配 fixture 却在未包含的数据形态上失败;更快的计划仍可能内存占用过高、锁时间过长或忽略生产环境有用索引。该方法增加步骤,对无静默丢失风险的只读仪表盘属于过度设计。若没有已知正确的结果集、无法安全构造代表性 fixture,就不要把本地通过当作数据库保证。对任何生成代码都应如此:在信任解释之前,先验证真正重要的行为。
#开发者 #工具 #SQL #SQLite #差分测试 #生成式AI #MonkeyCode #查询优化
@DevToolboxHub