首页 › tutorials › 造SQL索引医生Skill扫慢查询出方案

概述

本教程从零构建一个名为 sql-index-doctor 的 Claude Code Agent Skill:把一段慢 SQL 或 EXPLAIN 执行计划丢给 Claude,它自动识别反模式(隐式类型转换、左模糊、函数包裹列、OR 短路、回表过多等),输出可执行的 CREATE INDEX 方案。学完你将掌握 Skill 的三大底层机制——模型自主调用(model-invoked)、渐进式披露(progressive disclosure)、allowed-tools 权限收口。适合已会用 Claude Code 基础交互、想封装团队级数据库优化经验的后端工程师与 DBA。

前置条件


正文:七步造一个 SQL 索引医生

在动手前,先理解 Skill 与你熟悉的「斜杠命令」的根本区别,否则会把 Skill 写成命令、把命令写成 Skill。

维度 斜杠命令(slash command) Agent Skill
触发方 用户手动键入 /xxx 模型自主判断,依据 description
加载时机 立即把 prompt 塞进上下文 先只加载 name+description,命中后才读全文
典型场景 固定流程、一次性指令 可复用的领域能力、按需激活
权限 走默认权限模型 可用 allowed-tools 精准收口

一句话:Skill 是「能力」,命令是「动作」。索引优化是典型能力——你不知道 Claude 什么时候会碰到慢查询,但你希望它一旦碰到就按 DBA 的方法论来。这就是 Skill 的主场。

第 1 步:创建 Skill 目录

Skill 的本质是一个带 SKILL.md 的目录,没有数据库、没有注册中心。先决定作用域:

我们把 SQL 索引医生放在项目级,因为索引规范要团队统一。

mkdir -p .claude/skills/sql-index-doctor/{scripts,templates}
cd .claude/skills/sql-index-doctor
touch SKILL.md scripts/explain_scan.py templates/report.md reference.md

最终目录结构(这一步是渐进式披露的物理基础):

.claude/skills/sql-index-doctor/
├── SKILL.md              # 必需,总入口
├── reference.md          # 可选,反模式手册(按需读取)
├── scripts/
│   └── explain_scan.py   # 可选,EXPLAIN 解析器
└── templates/
    └── report.md         # 可选,输出模板

预期结果:ls .claude/skills/sql-index-doctor/ 能看到上述文件。此时 Claude 已经能发现这个 Skill,但还不会调用它,因为 SKILL.md 是空的。

常见问题:目录名必须与 name 字段一致吗?不强制,但强烈建议一致,便于排查。name 只允许小写字母、数字、连字符,最长 64 字符。

第 2 步:写 frontmatter——让模型「认识」你

打开 SKILL.md,写入 YAML 头部。这是整个 Skill 最关键的部分:description 决定了 Claude 在什么情境下会想起你。

---
name: sql-index-doctor
description: |
  MySQL/PostgreSQL 慢查询索引优化顾问。当用户做以下事情时使用本 Skill:
  (1) 粘贴一段 SQL 问「为什么慢」「怎么优化」;(2) 提供 EXPLAIN 执行计划要解读;
  (3) 排查索引缺失、回表过多、全表扫描;(4) 需要 CREATE INDEX 的可执行 DDL;
  (5) 优化 WHERE / JOIN / ORDER BY / GROUP BY 的查询性能。
  输出包含:命中反模式清单、EXPLAIN 关键字段判读、建议索引(含字段顺序与理由)、
  覆盖索引机会、可执行 DDL、回滚方案。
allowed-tools: Read, Grep, Glob, Bash(mysql:*), Bash(psql:*)
---

操作说明——description 的三条铁律:

  1. 写「什么时候用」比「我能做什么」更重要。模型靠 description 做语义匹配,触发词要覆盖用户的真实问法(「为什么慢」「EXPLAIN」「加索引」「全表扫描」)。
  2. 长度上限 1024 字符,但别为凑长度灌水——精准的关键场景胜过空洞的功能罗列。
  3. allowed-tools 是 Claude Code 专属字段,用逗号分隔,支持通配(如 Bash(mysql:*) 表示只能跑以 mysql 开头的命令)。这里我们故意不放 Write/Edit——索引医生只读 SQL、只连库查 EXPLAIN,不该改业务代码。

预期结果:保存后,在项目根目录问 Claude「What Skills are available?」,它会在列表里报出 sql-index-doctor 及其描述。

常见问题:Skill 不触发?九成是 description 没覆盖你的问法。比如用户说「这条 SQL 跑了 8 秒」,而你的描述里没有「慢」「耗时」「秒」这类触发词,模型就不会关联。修复:把用户的真实口语补进描述。

第 3 步:写正文——把 DBA 方法论固化成指令

frontmatter 之下是 Markdown 正文,这是命中后 Claude 实际读到的「操作手册」。把一个资深 DBA 脑子里那套分析流程逐条写下来:

# SQL 索引医生

## 分析流程(严格按序执行)

1. **提取谓词**:从 WHERE / JOIN ON / ORDER BY / GROUP BY 中
   抽出所有参与过滤、连接、排序、分组的列。
2. **识别反模式**:对照 reference.md 的反模式清单逐条扫描,
   命中即记录(见下方「必须告警的反模式」)。
3. **判读执行计划**:若用户提供了 EXPLAIN,优先读取;
   否则用 Bash 跑 `mysql -e "EXPLAIN <sql>"` 获取。
   重点看 type/access_method、key、rows、Extra 四列。
4. **设计索引**:按「等值列在前、范围列在后、排序列收尾」
   的原则组合联合索引,并判断能否做成覆盖索引。
5. **输出 DDL**:给出带注释的 CREATE INDEX 语句,
   并说明每列为何在这个位置。

## 必须告警的反模式(命中即写入报告「风险」段)

- 隐式类型转换:varchar 列与数字比较(`where phone = 13800138000`)
- 左模糊:`where name like '%张'` 无法走索引
- 函数包裹列:`where date(create_time) = '2026-07-11'`
- OR 跨列短路:`where a=1 or b=2` 两列无索引则全表扫
- 深度分页:`limit 1000000, 20`
- 回表过多:SELECT * 但索引无法覆盖

## EXPLAIN 关键字段判读表

| 字段 | 危险值 | 含义 |
|---|---|---|
| type / access | ALL、index | 全表扫或全索引扫 |
| key | NULL | 没用上任何索引 |
| rows | 远大于实际结果集 | 估算扫描行过多 |
| Extra | Using filesort、Using temporary | 需额外排序/临时表 |

## 输出格式

严格按 templates/report.md 的结构输出,不要自由发挥段落顺序。

操作说明:正文要像给实习生写的 SOP——可执行、有顺序、有判定标准。避免「请智能分析」「综合考虑」这类模糊指令,它们会让输出不可控。

预期结果:此时 Skill 已可工作。粘贴一条慢 SQL,Claude 会按上述五步走,输出结构化报告。

常见问题:正文写多长合适?记住渐进式披露——SKILL.md 只在命中时加载一次,太长会挤占上下文。经验值:核心流程 50–150 行,细节外移到 reference.md。

第 4 步:外移细节到 reference.md(渐进式披露实战)

第 3 步提到「对照 reference.md 的反模式清单」,现在把它写出来。这份文件默认不进上下文,只有 Claude 真的需要细节时才读取——这就是渐进式披露省 token 的关键。

# 索引反模式详解(reference.md)

## 反模式 1:隐式类型转换
**现象**:列是 varchar,查询写成数字字面量。
**示例**:`SELECT * FROM users WHERE phone = 13800138000`
**危害**:MySQL 会对 phone 列逐行套用类型转换函数,
       索引失效,退化为全表扫描。
**修复**:`WHERE phone = '13800138000'`(加引号)

## 反模式 2:函数包裹索引列
**现象**:对列施加函数后比较。
**示例**:`WHERE DATE(create_time) = '2026-07-11'`
**修复**:改为范围 `WHERE create_time >= '2026-07-11'
        AND create_time < '2026-07-12'`

## 反模式 3:左模糊
**现象**:`LIKE '%关键词'`
**说明**:B+Tree 索引按左前缀排序,左模糊无法定位。
       若必须全文检索,建议引入全文索引或 ES。

## 反模式 4:OR 跨无索引列
**修复**:为 OR 两侧列各建单列索引,优化器会用
       index_merge;或改写为 UNION ALL。

## 反模式 5:深度分页
**现象**:`LIMIT 1000000, 20`
**修复**:游标分页 `WHERE id > ? ORDER BY id LIMIT 20`

操作说明:reference.md 与 SKILL.md 的分工是——SKILL.md 说「做什么、按什么顺序」,reference.md 说「细节怎么判断、为什么」。前者是流程,后者是知识库。

预期结果:Claude 在分析时,遇到「函数包裹列」这类需要展开解释的情况,会主动 Read reference.md,而平时不加载它。

常见问题:怎么知道 Claude 有没有读 reference.md?开 /cost 或看工具调用日志,会出现一次 Read(.claude/skills/sql-index-doctor/reference.md)。

第 5 步:加 EXPLAIN 解析脚本——补模型短板

模型擅长语义分析,但数行数、做统计这种确定性任务交给脚本更准。我们写一个 Python 脚本,吃进 EXPLAIN 的 TSV 输出,吐出风险摘要。SKILL.md 通过 Bash 调用它。

# scripts/explain_scan.py
# 用法: mysql -e "EXPLAIN <sql>" -B | python3 explain_scan.py
import sys, csv, json

RISKY_TYPE = {"ALL", "index"}          # 全表扫 / 全索引扫
RISKY_EXTRA = ["Using filesort", "Using temporary"]

rows = list(csv.DictReader(sys.stdin, delimiter="\t"))
findings = []
total_rows = 0
for r in rows:
    access = r.get("type") or r.get("access_method") or ""
    key = (r.get("key") or "").strip()
    est = int(r.get("rows", 0) or 0)
    extra = r.get("Extra", "")
    total_rows += est
    if access in RISKY_TYPE:
        findings.append(f"行{r.get('id')}: 访问方式 {access}(疑似全扫)")
    if not key and access not in ("const", "system"):
        findings.append(f"行{r.get('id')}: 未命中任何索引")
    for kw in RISKY_EXTRA:
        if kw in extra:
            findings.append(f"行{r.get('id')}: {kw}")

print(json.dumps({
    "estimated_total_rows": total_rows,
    "risk_count": len(findings),
    "risks": findings or ["未发现明显执行计划风险"]
}, ensure_ascii=False, indent=2))

在 SKILL.md 正文里补一句调用方式:

当用户提供了 EXPLAIN 原始输出(TSV/表格)时,
先把它存为临时文件,再执行:
    python3 scripts/explain_scan.py < explain.tsv
拿到结构化风险摘要后,再结合反模式给出索引建议。

操作说明:Skill 目录里的脚本用相对路径引用(相对于 Skill 根目录),Claude 会自动在正确的工作目录下运行。

预期结果:喂一段含全表扫的 EXPLAIN,脚本输出 JSON 摘要,Claude 基于摘要给出索引方案。

常见问题:allowed-tools 里只放了 Bash(mysql:*) 和 Bash(psql:*),跑 python3 会不会被拦?会。脚本要靠 mysql -e "EXPLAIN" 管道喂入,所以实际执行的是 mysql 命令;若必须单独跑 python,把 Bash(python3:*) 加进 allowed-tools——但加上等于放开任意脚本执行,按需权衡。

第 6 步:用模板约束输出格式

索引建议如果格式飘忽,团队就很难批量复用。写一个输出模板,让每次报告结构一致:

<!-- templates/report.md -->
## 🩺 索引诊断报告

### 1. 查询概要
- 数据库类型:MySQL / PostgreSQL
- 涉及表:
- 扫描行数(EXPLAIN 估算):

### 2. 命中反模式
| 反模式 | 位置 | 严重度 | 修复建议 |
|---|---|---|---|

### 3. 索引方案
```sql
-- 建议索引(等值列 → 范围列 → 排序列)
CREATE INDEX idx_xxx ON tbl (col_a, col_b, col_c);

列顺序理由:

4. 覆盖索引机会

能否把 SELECT 的列也纳入索引,避免回表:

5. 验证与回滚

**操作说明**:在 SKILL.md 正文末尾加一句「输出格式严格遵循 templates/report.md」。这样 Claude 会先读模板再填空,而不是自由发挥。

### 第 7 步:测试、调优、上线

Skill 不是写完就灵,要像调 prompt 一样迭代 `description`。

**正向测试**(应当触发):
- 「这条 SQL 跑了 6 秒,`SELECT * FROM orders WHERE user_id=88 AND status=1 ORDER BY create_time DESC`」
- 「帮我看看这个 EXPLAIN,rows 怎么这么大」

**负向测试**(不应误触发):
- 「帮我写一个建表语句」——这是建表,不是优化,应触发建表类 Skill 而非本 Skill
- 「解释一下 B+Tree 原理」——这是科普,不需要出 DDL

**调优动作**:若负向误触发,在 `description` 里加**排除条件**,例如「仅用于已存在的查询的性能优化,不用于新建表结构设计」。

**预期结果**:正向命中、负向不命中,`/cost` 显示本 Skill 仅在相关对话被加载。

---

## 完整的 SKILL.md 总览

把上面几步拼起来,最终文件如下(可直接复制使用):

````markdown
---
name: sql-index-doctor
description: |
  MySQL/PostgreSQL 慢查询索引优化顾问。当用户做以下事情时使用本 Skill:
  (1) 粘贴一段 SQL 问「为什么慢」「怎么优化」;(2) 提供 EXPLAIN 执行计划要解读;
  (3) 排查索引缺失、回表过多、全表扫描;(4) 需要 CREATE INDEX 的可执行 DDL;
  (5) 优化 WHERE / JOIN / ORDER BY / GROUP BY 的查询性能。
  不用于新建表结构设计。输出包含反模式清单、EXPLAIN 判读、建议索引、覆盖索引机会、DDL、回滚方案。
allowed-tools: Read, Grep, Glob, Bash(mysql:*), Bash(psql:*)
---

# SQL 索引医生

## 分析流程(严格按序)
1. 提取谓词:WHERE / JOIN ON / ORDER BY / GROUP BY 的列
2. 识别反模式:对照 reference.md 逐条扫描
3. 判读执行计划:用户提供则读,否则 `mysql -e "EXPLAIN <sql>"`;
   有 TSV 原始输出时先跑 `python3 scripts/explain_scan.py < explain.tsv`
4. 设计索引:等值列在前、范围列在后、排序列收尾
5. 输出 DDL:带注释的 CREATE INDEX + 列顺序理由

## 必须告警的反模式
隐式类型转换 / 左模糊 / 函数包裹列 / OR 跨列短路 / 深度分页 / 回表过多

## EXPLAIN 判读
- type/access 为 ALL/index → 全扫风险
- key 为 NULL → 未命中索引
- rows 远大于结果集 → 扫描过多
- Extra 含 Using filesort/Using temporary → 额外排序

## 输出格式
严格遵循 templates/report.md。细节判断依据见 reference.md。
````

---

## 实战案例:救活一条 6 秒的订单查询

### 现场还原

某电商后台一条订单查询,数据量 800 万行,稳定跑 6 秒:

```sql
SELECT id, order_no, user_id, amount, status, create_time
FROM orders
WHERE user_id = 88
  AND status = 1
  AND DATE(create_time) = '2026-07-11'
ORDER BY create_time DESC
LIMIT 20;

Claude 的诊断(Skill 自动激活)

第 1 步——提取谓词:user_id(等值)、status(等值)、create_time(函数包裹)、create_time(排序)。

第 2 步——识别反模式:命中「函数包裹列」——DATE(create_time) = '2026-07-11',索引会失效。

第 3 步——改写 + 设计索引。先消除反模式,再建索引:

改写后的等价查询(去掉函数):

WHERE user_id = 88 AND status = 1
  AND create_time >= '2026-07-11 00:00:00'
  AND create_time <  '2026-07-12 00:00:00'

第 4 步——索引方案:

-- 联合索引:等值列(user_id, status) 在前,
-- 范围列(create_time) 在后,同时支撑 ORDER BY,避免 filesort
CREATE INDEX idx_uid_status_time ON orders (user_id, status, create_time);

列顺序理由:user_id 选择性最高放最左;status 等值过滤放第二;create_time 既是范围又是排序列放最后,可同时满足过滤与 ORDER BY,消除 Using filesort。

第 5 步——覆盖索引评估:SELECT 列含 order_no、amount,若全部塞进索引会让索引臃肿。本例回表 20 次(LIMIT 20)成本可接受,不强行做覆盖索引,权衡得当。

输出报告(节选)

反模式 位置 严重度 修复
函数包裹列 DATE(create_time) 高 改范围查询
指标 优化前 优化后
访问方式 ALL(全表扫) ref
估算扫描行 ~8,000,000 ~50
Extra Using filesort (无)
耗时 6.0s 0.02s

回滚

DROP INDEX idx_uid_status_time ON orders;

这个案例完整演示了 Skill 的价值:把一位 DBA 的判断流程,固化成团队任何人都能调用的能力,且每次输出结构一致、可审计、可回滚。


工作流程图

flowchart TD
    A[用户粘贴慢SQL/EXPLAIN] --> B{Claude语义匹配<br/>description?}
    B -->|命中| C[加载 SKILL.md 全文]
    B -->|不命中| Z[走普通对话]
    C --> D[allowed-tools 收口权限]
    D --> E[1.提取谓词]
    E --> F[2.对照 reference.md 扫反模式]
    F --> G{用户提供EXPLAIN?}
    G -->|是| H[跑 scripts/explain_scan.py]
    G -->|否| I[Bash: mysql -e EXPLAIN]
    H --> J[3.结构化风险摘要]
    I --> J
    J --> K[4.设计联合索引<br/>等值→范围→排序]
    K --> L[5.评估覆盖索引]
    L --> M[按 templates/report.md 输出]
    M --> N[给出DDL + 验证 + 回滚]

进阶:让 Skill 更稳的三个习惯

  1. description 写触发词与排除词。光说能做什么不够,要说「在 X 场景用,在 Y 场景不用」,模型才不会误触发。本例明确排除「新建表结构设计」。
  2. 确定性逻辑下沉为脚本。EXPLAIN 解析、行数统计这类工作交给 scripts/ 下的 Python,模型只做语义判断与方案设计。脚本可单测、可版本化,比纯 prompt 更可靠。
  3. 把团队规范外移成 reference。公司的索引命名规范、禁用规则、分库分表边界条件,都沉淀在 reference.md,新人换项目也能继承。

延伸阅读


训练营适用性评估


参考来源

  1. Extend Claude with skills — Claude Code Docs — 官方 Skill 创建、frontmatter、allowed-tools、渐进式披露的权威说明
  2. How to create custom skills — Claude Help Center — 官方帮助中心对 Skill 目录结构与作用域的说明
  3. Inside Claude Code Skills: Structure, prompts, invocation — 第三方对 Skill 模型自主调用与 description 触发机制的深度拆解
  4. Claude Code Skill Frontmatter: Every YAML Option Explained — name/description/allowed-tools 各字段的逐项释义与校验规则
  5. Equipping agents for the real world with Agent Skills — Anthropic 工程团队关于 Skill 架构设计与真实落地的官方工程博客

[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。

51学通信知识星球海报-扫码加入