接手一个服役超过五年的老业务系统,最让人窒息的不是框架老旧,而是那些动辄上百行、不仅没有注释还到处散落着各种硬编码的“屎山” SQL。上周,我们的核心报表接口因为慢查询导致了线上数据库 CPU 飙升,DBA 扔过来几条耗时超过 5 秒的复杂关联查询,要求限期整改。
面对这种连原作者都离职好几年的代码,如果靠人工去梳理嵌套逻辑,或者挨个加EXPLAIN试错,效率实在太低,而且极容易改错原有的业务语义。为了尽快摸清这些长 SQL 的意图并给出优化方案,我决定把“代码逆向解释”和“语句重构”的任务交给大模型来做。但在处理这种充满隐晦逻辑的遗留代码时,单一模型的表现往往不够稳定:有些模型擅长解释业务意图,但在重写语法时容易犯错;有些模型则能敏锐地发现索引失效,但重构的代码可读性一般。
为了搞清楚哪个模型给出的重构建议更靠谱,我测试时用到过一个多模型聚合环境,可以在同一界面里切换 ChatGPT、Claude、Gemini、Grok 等模型,非常适合把同一段脱敏后的 SQL 喂进去做同题复测,省去了在不同网页间反复拷贝上下文的麻烦。经过几轮控制变量的横向对比,我整理出了一套利用 AI 辅助重构遗留 SQL 的实战工作流。
一、不可逾越的红线:库表结构与数据的自动化脱敏
在把任何业务代码丢给大模型之前,数据和结构的脱敏是绝对的红线。很多新手为了图省事,直接把原生报错堆栈和真实表名贴给 AI,这存在极大的数据泄露风险。
大模型不需要知道你的表叫alipay_user_transaction_record也能帮你优化查询。在将 SQL 送去分析前,我习惯用一个简单的 Python 脚本对 DDL 和 SQL 文本进行混淆替换。
核心脱敏逻辑主要针对以下几点:
- 替换真实表名与敏感字段:将
tb_user_core替换为table_A,将phone_number替换为field_phone。 - 屏蔽真实业务数据:如果是带参数的慢查询日志,必须用正则把真实的手机号、身份证号、金额替换为占位符(如
[REDACTED_PHONE]、100.00)。 - 剔除注释里的商业机密:有些老代码的注释里会写明合作方的佣金比例或敏感规则,需一并抹除。
经过清洗,一份包含真实商业上下文的 SQL 就变成了一份纯粹的“关系型代数问题”,此时再喂给模型才是安全的。
二、构建结构化 Prompt:从逆向解析到重构输出
很多时候,直接把 SQL 丢给 AI 并附上一句“帮我优化一下”,得到的往往是毫无意义的废话,或者它会随手捏造一个你当前数据库版本根本不支持的函数。
为了让大模型的输出具备工程可用性,我采用了基于 XML 标签的结构化 Prompt,并强制要求模型先“解释”再“重构”。这能有效验证模型是否真正理解了原代码的语义。
以下是我在多模型环境中复测时使用的一套标准 Prompt:
角色设定:你现在是一位拥有 10 年经验的高级 MySQL DBA,精通 SQL 调优与执行计划分析。
任务背景:我们需要优化一段历史遗留的慢查询 SQL。使用的数据库版本是 MySQL 8.0。
<ddl_info>
[附上脱敏后的建表语句,必须包含主键和当前已有索引]</ddl_info>
<slow_sql>
[附上脱敏后的千行慢查询 SQL]</slow_sql>输出要求:
- 语义逆向:请用自然语言分步骤解释这段 SQL 到底在查什么数据,逻辑链路是什么?
- 瓶颈诊断:指出当前 SQL 为什么慢(如全表扫描、隐式类型转换、笛卡尔积等)。
- 重构代码:使用 CTE (WITH…AS) 语法对这段 SQL 进行可读性重构,保持业务语义完全一致。
- 索引建议:给出配合重构后 SQL 的复合索引创建语句(DDL)。
三、多模型横向复测:不同模型的调优专长差异
在使用同一套 Prompt 和脱敏 SQL 进行复测时,我发现不同模型在处理这类逻辑严密的数据库任务时,展现出了截然不同的能力侧重点。
1. 逻辑解构与可读性重构:Claude 的主场
在面对极度冗长、嵌套了四五层SELECT的子查询时,Claude 展现出了极强的上下文梳理能力。
它能非常清晰地将原本混成一团的嵌套查询,按逻辑模块拆解成多个CTE(公用表表达式)。它给出的重构代码注释详尽,几乎把“屎山”重新组织成了带有清晰业务模块的管道流代码。但在具体的底层索引计算上,Claude 偶尔会忽略 MySQL 的某些特定限制。
2. 底层优化与索引命中:ChatGPT 的专长
ChatGPT 在理解 MySQL 执行引擎的行为上表现得更像个老手。
在一次复测中,原 SQL 的WHERE条件里存在对日期字段的函数包裹(例如WHERE DATE(create_time) = '2023-10-01'),导致了索引失效。ChatGPT 一针见血地指出了这个问题,并将其重写为范围查询(WHERE create_time >= '2023-10-01' AND create_time < '2023-10-02')。在复合索引的最左匹配原则建议上,它的输出也极为标准。
3. 代码直出与边界排查:Grok 的破局
当我们把排查范围从单一 SQL 扩大到包含事务代码的复杂堆栈时,Grok 的表现令人印象深刻。它不会输出太多正确的废话,而是直接切中要害。比如它曾敏锐地发现表 A 的user_id是VARCHAR,而表 B 关联的user_id是BIGINT,这种隐式类型转换导致了关联查询时完全无法走索引。这种细节如果是肉眼排查,可能要盯半天表结构才能发现。
通过这种横向比对,我们最终融合了 Claude 拆解的 CTE 结构、ChatGPT 建议的范围查询语法以及 Grok 发现的类型转换修复,拼装出了一份高质量的重构版本。原先耗时 5.2 秒的报表查询,在打上新索引并替换 SQL 后,响应时间直接降到了 120 毫秒以内。
四、AI 辅助数据库优化的风险边界
虽然大模型在遗留系统重构中展现了惊人的效率,但在实际落地中,这套工作流必须搭配严格的人工验证机制。切忌将 AI 的输出视为真理。
警惕“幻觉函数”的出现
部分模型可能会混淆不同数据库方言(Dialect)。比如你明确指定了 MySQL,它可能还会顺手写一个 Oracle 专属的分析函数,或者在 MySQL 5.7 环境下给你用 8.0 才有的窗口函数。所有的重构代码必须在本地沙箱跑通语法树。数据分布的“上帝视角”缺失
AI 只能基于 DDL 和 SQL 文本进行静态分析,它不知道你生产环境的数据基数(Cardinality)。比如它建议你对status字段建索引,但如果该表 99% 的记录status都是 1,这个索引不但毫无意义,还会拖慢写入。索引的最终拍板,必须结合线上真实的数据分布规律。语义等价性的盲区
重构前后的 SQL,性能再快,如果查出来的数据不一致,那就是重大生产事故。大模型在简化LEFT JOIN或是把子查询改为INNER JOIN时,极容易改变结果集的行数(例如产生笛卡尔积膨胀,或过滤掉了原本该有 NULL 值的行)。因此,双写对比验证或严密的单元测试,是不可省略的一环。
五、总结与实践建议
从“肉眼看屎山”到“结构化拆解”,大模型为后端开发者提供了一个极其强大的代码逆向与重构引擎。面对历史技术债,我们不再需要望而却步。
对于想要在团队内推行类似实践的开发者,我的建议是:
首先,从低风险的非核心查询(如内部运营后台的报表)开始练手;其次,写好本地的脱敏脚本,用清晰的结构化指令约束 AI;最后,遇到复杂的执行逻辑时,不妨借助聚合工具引入多个模型进行交叉比对,综合各家之长。
只要坚守脱敏红线,并把 AI 的输出当作“需要经过严格 Review 的初级 DBA 建议”,你就能在应对遗留系统重构时,大幅减轻心智负担,真正实现研发效能的跃升。