AI Agent 技能分享|SQL 性能诊断与优化
把 SQL 写短,不等于把 SQL 写快;先拿到证据,才能避免"优化后更慢"。
遇到慢 SQL,常见反应是:改 JOIN、套 CTE、加索引、上 NOLOCK。这些动作有时能解决问题,也可能改变查询结果、放大写入成本,甚至把偶发问题变成长期隐患。
更稳妥的方式是把 SQL 优化当成一次诊断,而不是盲目改写。我将这套流程整理为 SQL 性能诊断与优化(sql-performance-optimizer) 技能,专门帮助 AI 先厘清范围和证据,再给出可验证的改进建议。
第一步:先选优化范围
"优化 SQL"至少可能指五种不同事情:
- 执行性能:扫描、排序、聚合、分页、JOIN、过滤条件是否高效。
- 索引与数据设计:现有索引是否命中、组合索引顺序是否合理、字段类型是否一致。
- 业务逻辑:是不是读了不该读的数据、重复计算,是否适合缓存或预聚合。
- 兼容性与正确性:数据库版本、NULL 语义、隐式转换、并发和隔离级别风险。
- 完整检查:以上都看,但按收益优先级排序。
这一步很重要。比如一个报表慢,不一定是 SQL 写法问题,可能是一次性拉取了三年的明细;这时仅加索引只是在给错误的访问方式打补丁。
第二步:没有执行计划,不承诺性能提升
真正需要的证据通常包括:
- 实际执行计划或
EXPLAIN输出 - 耗时、逻辑读/物理读、CPU 与返回行数
- 实际参数值、相关表数据量
- 现有索引定义
- 数据库产品与版本
有了这些信息,才能分辨是全表扫描、索引选择错误、参数嗅探、排序溢出,还是 JOIN 后行数爆炸。
没有这些信息时,可以做静态分析,但结论应标记为"待验证的假设",而不是信誓旦旦地说某个索引一定有效。
一个典型例子:别让函数包住索引列
下面的条件看起来很自然:
sql
WHERE CONVERT(date, o.created_at) = @date
但它可能让优化器难以利用 created_at 上的普通索引,因为需要先对每一行做转换。
在确认"按自然日筛选"的语义后,可以改为范围查询:
sql
WHERE o.created_at >= @start_time
AND o.created_at < @end_time
这类改动通常更利于索引查找,但仍要结合字段类型、时区和数据库方言验证。日期边界写错,会得到一条"很快但不对"的 SQL。
优化建议应分四类输出
一份实用的诊断结论,不该只给一段重写 SQL,而应区分:
| 类别 | 应回答的问题 |
|---|---|
| SQL 改写 | 如何最小化修改,并保证结果集语义不变? |
| 索引/数据设计 | 需要什么键列和包含列?读收益与写入、空间成本是什么? |
| 业务方案 | 能不能减少数据范围、分页、缓存或预聚合? |
| 平台/版本方案 | 语法是否受版本限制,兼容替代方案是什么? |
尤其是索引建议,要给出明确的验证与回滚路径。索引会改善一部分查询,也会带来写入维护和存储成本;"多建几个总没错"往往不是生产环境的好主意。
这些"速效药"为何要谨慎
- 查询提示/强制计划:可能只适合当前数据分布,后续数据增长后反而恶化。
NOLOCK:可能读到未提交、重复或遗漏的数据,不是通用提速手段。- 盲目加索引:可能与现有索引重复,并拖慢写入。
- 把
NOT IN随意改写:涉及 NULL 时,结果语义可能变化。
优化的目标不是让某次测试变快,而是在代表性参数、真实并发下持续稳定地变快。
用什么指标验收
改动前后建议对比:
text
耗时 / CPU / 逻辑读 / 物理读 / 返回行数 / 计划形状 / 并发影响
只要这些指标没有对齐,就还不能确定"优化成功"。
如何使用
把 SQL、数据库类型和你观察到的现象发给支持 Skills 的 AI Agent,再说:
text
请用 sql-performance-optimizer 做完整检查。
它会先确定优化范围,索取最有价值的证据,再按收益和风险排序输出 SQL、索引、业务层面的建议。
想获取这个技能,或看看我整理的其他 AI Agent 实战技能:
你遇到过最"反直觉"的慢 SQL 原因是什么?欢迎留言交流。