结论先说:AI 能把慢查询的排查从「凭经验猜」变成「按执行计划逐项核对」,但索引该不该加、加了会不会拖慢写入,必须在真实数据量上用 EXPLAIN 验证后再上线。把 AI 当诊断助手而不是决策者,是这套流程有效的前提。
大多数人的用法是错的:把一条 SQL 丢给模型,问「怎么优化」,拿到一条 CREATE INDEX 就执行。这种做法有三个问题——模型看不到你的数据分布,看不到已有索引,也看不到这条查询在业务里的调用频率。缺了这三项,任何优化建议都只是猜测。
一、喂给 AI 之前,先备齐三样东西
这一步省不掉。少了任何一样,AI 给出的建议质量都会断崖式下降。
- 执行计划原文:MySQL 用
EXPLAIN ANALYZE,PostgreSQL 用EXPLAIN (ANALYZE, BUFFERS),SQL Server 用实际执行计划。要原文,不要你的转述。 - 表结构与现有索引:
SHOW CREATE TABLE或\d 表名的完整输出,包含字符集、排序规则、联合索引的列顺序。 - 数据量级与倾斜度:表总行数、 WHERE 条件命中比例、关键列的唯一值数量(区分度)。
区分度这一项最常被忽略。同样是给 merchant_id 建索引,如果这个字段全表只有 10 个不同值,索引基本没用;把 create_time 放在联合索引首列才是正解。数据建模阶段确定的字段区分度,到这里就是索引设计的输入。
可直接复用的提示词骨架
以下是慢查询的上下文,请按三步回答:
1. 读执行计划,指出全表扫描、临时表、 filesort 分别出现在哪一步;
2. 定位导致索引失效的具体写法,逐条给出依据;
3. 给出候选索引方案,并说明每个方案对写入的代价。
【执行计划】
...
【表结构与现有索引】
...
【数据量级】
表 A 约 1200 万行,条件命中约 3%,create_time 区分度高,status 仅 5 个取值
【查询】
...
要求它「逐条给出依据」,是为了避免它直接跳到结论。实测下来,这一步能把建议可用率从大约一半提到八成以上。
二、让 AI 做三轮诊断,而不是一次性给结论
第一轮:读执行计划,圈出三个关键词
让模型先只做定位,不做改写。重点看三件事:type=ALL 或 Seq Scan(全表扫描)、Using temporary(临时表)、Using filesort(额外排序)。这三项通常决定了 80% 的耗时。
第二轮:定位「索引失效」的写法
这是 AI 最擅长的部分,因为模式固定。常见六类:
- 列上套函数:
WHERE YEAR(created_at) = 2024让索引彻底失效,改成created_at >= '2024-01-01' AND created_at < '2025-01-01'。 - 隐式类型转换:字符串列用数字查,或数字列传了字符串,索引直接不走。
- 前导列不匹配:联合索引
(a, b, c),只用b和c做条件时无法命中。 - OR 连接:部分场景下会让优化器放弃索引,可改写为 UNION ALL 。
- 不等值与 NOT IN:选择性差,优化器可能主动放弃索引。
- 深分页:
LIMIT 100000, 20需要先扫过十万行,改成基于上一页最大 id 的游标分页。
第三轮:评估索引代价
这一步必须让模型把「代价」也写出来,不能只写收益。每加一个索引,写入时就要多维护一棵 B+ 树,磁盘占用和主从延迟都会上升。覆盖索引虽然能避免回表,但联合列越多,写放大越明显。
三、实战:一次分页聚合查询的重写
GitHub 工程博客公开过一个很典型的案例,值得完整拆一遍。他们要提供仓库的 star 增长趋势,最初的做法是让调用方分页拉取 stargazers 列表再自行聚合——以 react/react 约 25.1 万 star 计算,拉完完整历史需要约 2511 次请求;更糟的是分页实现有缺陷,每个请求都会对全量 star 做一次 count 。
他们试过「先聚合再分页」:按天或周分桶,排序后再 LIMIT/OFFSET 。看起来合理,但 SQL 的实际执行顺序是——先取出全部匹配的 star,逐行分桶,排序所有桶,最后才应用 LIMIT 和 OFFSET 。也就是说,返回的数据变少了,但分组和排序的开销一点没少。按这个方式读完整历史,数据库要检查约 600 万行,因为每次请求都要把 25.1 万个 star 重新处理一遍。
最终方案只改了一处顺序:把 page 和 per_page 翻译成一个带索引的 created_at 日期范围,先用索引把行收窄到目标周,再分组。
朴素写法:全部匹配行 → 按天/周分桶 → 排序所有桶 → OFFSET/LIMIT → 返回
优化写法:索引日期范围 → 范围内分组 → 每日计数 → 返回
结果:请求数从约 2511 降到约 24,数据库检查的行数从约 600 万降到约 25.1 万——每个 star 大约只被检查一次。
这个案例给出的方法论比结论更值钱:LIMIT 只减少返回量,不减少计算量;真正的优化是让过滤条件在聚合之前生效,并且让索引承担这次过滤。把这段思路交给 AI 时,你可以直接问它「这条查询的过滤条件能不能下推到扫描阶段」。
AI 在 SQL 优化上的四个能力边界
做不了的:判断你的实际数据分布、预测线上并发下的锁竞争、感知主从延迟、替你承担上线风险。
会犯的错:凭字段名臆造索引(建议建一个根本不存在的列)、忽略已有联合索引导致重复建索引、把覆盖索引推荐成万能解。
对策:所有索引建议先查现有索引再评估;所有改写先在从库或预发环境跑 EXPLAIN 对比。
四、验证闭环:优化完不等于结束
- 在同等数据量上跑
EXPLAIN ANALYZE,确认扫描行数和实际耗时两个指标都下降,只降其中一个不够。 - 用慢日志记录优化前后的 P95 耗时,取同一时段对比,避开业务高峰期波动。
- 检查新索引是否让同表其他查询的执行计划变差——优化器改走新索引的情况经常发生。
- 观察写入侧的延迟与磁盘增长,尤其是大表的批量导入任务。
- 准备好回滚语句(DROP INDEX),并确认删除索引不会触发长时间的锁表。
如果你同时在做把数据库接进 AI 工具链的事情,可以先看MCP 服务器接数据库的权限与查询边界设计,那里讨论的是「让 AI 直接查库」时的安全边界,和本文的优化流程是上下游关系。
五、一份速查清单
| 现象 | 先查什么 | 常见解法 |
|---|---|---|
| 全表扫描 | WHERE 条件列是否有索引、是否套了函数 | 改写条件 + 补索引 |
| Extra 出现 filesort | ORDER BY 与索引列顺序是否一致 | 调整联合索引列顺序 |
| 深分页慢 | OFFSET 值是否过大 | 改成 id 游标分页 |
| 聚合查询慢 | 过滤条件是否在聚合之前生效 | 先按索引范围收窄再分组 |
| 加了索引没变快 | 区分度、隐式转换、统计信息是否过期 | ANALYZE TABLE 后重测 |






评论 (0)