<!-- title: 造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。

## 前置条件

- Claude Code 1.0+（本机已安装，终端可执行 `claude --version`）
- 一段真实的慢查询 SQL 或一份 `EXPLAIN` 输出（MySQL 5.7+/8.x 或 PostgreSQL 12+）
- 基本的索引常识：聚簇索引、覆盖索引、最左前缀
- 可选：本地有 `mysql` / `psql` 客户端用于回放验证

---

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

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

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

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

### 第 1 步：创建 Skill 目录

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

- 个人级：`~/.claude/skills/` —— 只对你生效，适合实验
- 项目级：`.claude/skills/` —— 随 git 共享给团队，适合沉淀规范
- 插件级：随插件安装，跨项目复用

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

```bash
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 在什么情境下会想起你。

```yaml
---
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 脑子里那套分析流程逐条写下来：

```markdown
# 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 的关键。

```markdown
# 索引反模式详解（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` 调用它。

```python
# 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 正文里补一句调用方式：

```markdown
当用户提供了 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 步：用模板约束输出格式

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

```markdown
<!-- 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. 验证与回滚
- 验证：`EXPLAIN` 应出现 `type=ref/range`、`rows` 显著下降
- 回滚：`DROP INDEX idx_xxx ON tbl;`
```

**操作说明**：在 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 步——改写 + 设计索引**。先消除反模式，再建索引：

改写后的等价查询（去掉函数）：
```sql
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 步——索引方案**：

```sql
-- 联合索引：等值列(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 |

### 回滚

```sql
DROP INDEX idx_uid_status_time ON orders;
```

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

---

## 工作流程图

```mermaid
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`，新人换项目也能继承。

---

## 延伸阅读

- 官方 Skills 总览：`https://code.claude.com/docs/en/skills`
- Skills 最佳实践：`https://docs.claude.com/en/docs/agents-and-tools/agent-skills/best-practices`
- 工程博客《Equipping agents for the real world with Agent Skills》：`https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills`
- Skill 内部机制深度解析：`https://mikhail.io/2025/10/claude-code-skills/`

---

## 训练营适用性评估

- **适用类型**：后端工程训练营、数据库调优实战营、AI 辅助研发训练营。需学员有 SQL 与索引基础，不建议纯零基础。
- **教学时长**：建议 2 课时。第 1 课时讲 Skill 三大机制并搭出骨架，第 2 课时让学员各自造一个**自己业务领域**的 Skill（如风控规则、日志格式、配置校验），互测触发率。
- **考核点**：① 能否写出会自动触发且不误触发的 `description`；② 能否合理用 `allowed-tools` 收口权限；③ 能否用脚本 + reference.md 践行渐进式披露；④ 输出报告是否结构一致、可审计。
- **延伸作业**：把 SQL 索引医生扩展成「索引体检」——主动扫全库的慢查询日志，批量出方案（结合 `-p` 批处理模式与本 Skill）。

---

## 参考来源

1. [Extend Claude with skills — Claude Code Docs](https://code.claude.com/docs/en/skills) — 官方 Skill 创建、frontmatter、`allowed-tools`、渐进式披露的权威说明
2. [How to create custom skills — Claude Help Center](https://support.claude.com/en/articles/12512198-how-to-create-custom-skills) — 官方帮助中心对 Skill 目录结构与作用域的说明
3. [Inside Claude Code Skills: Structure, prompts, invocation](https://mikhail.io/2025/10/claude-code-skills/) — 第三方对 Skill 模型自主调用与 description 触发机制的深度拆解
4. [Claude Code Skill Frontmatter: Every YAML Option Explained](https://allahabadi.dev/blogs/ai/claude-code-skills-frontmatter-complete-guide/) — `name`/`description`/`allowed-tools` 各字段的逐项释义与校验规则
5. [Equipping agents for the real world with Agent Skills](https://www.anthropic.com/engineering/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学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
