<!-- title: 造SQL慢查询诊断Skill -->
## 概述

本教程面向已经能在终端里跑通 Claude Code、想从"用别人写好的东西"升级到"自己造能力"的开发者。你将从一个空目录开始，完整造出一个 **`sql-slow-query-doctor`**（SQL 慢查询诊断）Skill：把一段 SELECT/UPDATE 语句丢给它，它自动揪出 `SELECT *`、前导通配 `LIKE '%xx'`、列上套函数、缺 WHERE、`ORDER BY RAND()` 等性能杀手，给出按严重度排序的改写建议与索引方案。过程中你会吃透 Skill 的四件套——**frontmatter、正文指令、挂载脚本、渐进披露**——并掌握"安装→发现→手动调用→自动触发→测试调优"的完整闭环。

## 前置条件

- Claude Code 已安装并能正常对话（命令行 `claude` 可用）
- 一台装了 Python 3.8+ 的机器（本例脚本用纯标准库，无需 pip 装包、无需数据库）
- 能看懂基本的 YAML 和 Python
- 建议准备一个测试目录，例如 `~/skill-lab/`，里面放几个 `.sql` 样例

## 正文：八步造出一个真实可用的 Skill

### 第 1 步：先搞清 Skill 到底是什么

很多人把 Skill 想得很玄。一句话：**Skill 就是一个带说明书（`SKILL.md`）的文件夹**，里面可以再放脚本、参考资料、模板。Claude 启动时会把所有可见 Skill 的 `name` 和 `description` 扫一遍（这一步很便宜），**只有在判断某个 Skill 跟当前任务相关时，才会把它的 `SKILL.md` 正文读进来**。这就是官方反复强调的"渐进披露"——几百个 Skill 摆在那儿也不会撑爆上下文，因为正文是按需加载的。

一个 Skill 的物理形态长这样：

```
.claude/skills/sql-slow-query-doctor/
├── SKILL.md          # 唯一必需：说明书
├── scripts/
│   └── sql_doctor.py # 可选：挂载的确定性脚本
└── references/
    └── rules.md      # 可选：深入规则，按需引用
```

两种摆放位置，效果不同：

| 位置 | 作用域 | 适用场景 |
|---|---|---|
| `<项目>/.claude/skills/` | 仅当前项目 | 团队共享、随仓库走 |
| `~/.claude/skills/` | 个人全局 | 跨项目复用的小工具 |

本例放项目级，方便后续提交进 Git 团队共享。

### 第 2 步：规划目录与命名

命名遵循三点：**全小写、kebab-case、动词或名词短语**。`sql-slow-query-doctor` 比 `sql_helper` 或 `DB工具` 都好——前者一眼能猜出能力边界。目录名就是 Skill 名，别用空格、别用中文。

```bash
mkdir -p .claude/skills/sql-slow-query-doctor/scripts
mkdir -p .claude/skills/sql-slow-query-doctor/references
cd .claude/skills/sql-slow-query-doctor
```

### 第 3 步：写 frontmatter——这是 Skill 的"招聘广告"

frontmatter 是 Skill 能否被自动触发的命根子。Claude 决定要不要读你的正文，**几乎完全看 `description`**。先看一份合格的：

```yaml
---
name: sql-slow-query-doctor
description: Analyzes a SQL SELECT/UPDATE/DELETE statement for performance anti-patterns (SELECT *, leading-wildcard LIKE, functions on indexed columns, missing WHERE, ORDER BY RAND(), large LIMIT) and returns prioritized, severity-ranked optimization suggestions with index recommendations. Use this skill when the user shares a slow or suspect SQL query, asks to optimize/tune a query, reports high database load, or wants an index plan.
allowed-tools: Bash, Read, Write
---
```

逐字段拆解：

- **`name`**：和目录名一致，kebab-case。
- **`description`**：三要素缺一不可——**做什么（What）+ 何时用（When）+ 关键词（Keywords）**。注意：①用**英文**写，匹配更稳，因为触发判断走的是底层语义匹配；②把"何时用"用 `Use this skill when…` 显式写出来；③塞进用户可能用的同义词（optimize / tune / slow query / high load / index），这直接决定命中率。
- **`allowed-tools`**：白名单。本例需要 `Bash`（跑 Python 脚本）、`Read`（读 `.sql` 文件）、`Write`（落优化报告）。**收窄这个列表是最被忽视的安全实践**——一个只该读查询的 Skill 不该有改生产代码的权限。

可选但重要的字段：

- **`disable-model-invocation: true`**：设了它，Claude 不会自动触发，**只能靠你显式 `/sql-slow-query-doctor` 调用**。适合那种"我不想让 AI 自作主张用"的敏感 Skill（比如会发邮件、会删数据的）。诊断类 Skill 要的是"用户一问就自动接手"，所以**不设**这个字段。

### 第 4 步：写正文指令——把自己当成新实习生写交接文档

正文不需要长，关键是**把决策点和操作步骤写死**。原则：能用脚本确定的事绝不靠模型"想"，模型只负责调度和解读。

```markdown
# SQL 慢查询诊断

## 你的工作流程（严格按序执行）

1. 获取待诊断的 SQL：
   - 用户直接贴了语句 → 用它；
   - 用户给了 `.sql` 文件路径 → 用 Read 读取；
   - 都没有 → 主动问用户要，不要编造。

2. 把 SQL 写入临时文件 `/tmp/_sqldoctor_input.sql`（脚本从文件读取，避免 shell 转义把引号吃掉）。

3. 运行确定性分析器：
   ```bash
   python3 scripts/sql_doctor.py /tmp/_sqldoctor_input.sql
   ```
   它输出 JSON，内含 findings 数组，每条带 severity / rule / problem / suggestion。

4. 把 JSON 翻译成用户能看懂的报告：
   - 按 severity（high→medium→low）排序；
   - 每条引用原文里出问题的片段（给出行号或子串）；
   - 末尾给一节"建议索引"，仅当命中索引相关规则时输出。

5. 绝不要修改用户的项目代码，除非用户明确要求改。输出仅限诊断报告。

## 边界
- 只做**静态**分析，不连数据库、不执行 SQL。
- DDL（CREATE/ALTER）不分析，提示用户本工具只看 DML。
```

注意第 2 步那个"先落临时文件"的小设计——这是踩坑换来的经验：直接把多行 SQL 当命令行参数传，引号和换行分分钟让脚本解析出错。**让脚本读文件，是最省心的接口。**

### 第 5 步：挂载确定性脚本——Skill 的"肌肉"

这是 Skill 区别于"纯提示词"的关键：**把可重复、可验证的逻辑下沉到脚本**，模型负责调用，脚本负责算。下面这份分析器是纯标准库，复制即可运行：

```python
#!/usr/bin/env python3
"""sql_doctor.py — SQL 慢查询静态分析，零依赖、不连库。"""
import re, sys, json

RULES = []  # 每条规则: (id, severity, regex, problem_tpl, suggestion_tpl)

def rule(rid, sev, pattern, problem, suggestion):
    RULES.append((rid, sev, re.compile(pattern, re.IGNORECASE), problem, suggestion))

# ---- 注册规则 ----
rule("SELECT_STAR", "medium",
     r"\bselect\s+\*\s+from", "SELECT * 拉取全部列，浪费 IO 且破坏覆盖索引",
     "只查需要的列；若走索引，把所需列都纳入索引以触发 index-only scan")

rule("LEADING_WILDCARD_LIKE", "high",
     r"like\s+'\s*%", "前导通配 LIKE '%xx' 无法走 B-Tree 索引，触发全表扫描",
     "改全文索引，或改右匹配 LIKE 'xx%'；业务允许时用前缀匹配")

rule("FUNC_ON_INDEX", "high",
     r"where\s+\w+\s*\(.*\)\s*=", "WHERE 列上套函数/运算会让索引失效",
     "把函数改写到常量侧，如 DATE(col)=x 改为 col >= x AND col < x+1")

rule("MISSING_WHERE", "high",
     r"^(select|update|delete)\b(?!.*\bwhere\b)", "无 WHERE 的 DML 会全表扫描/全表更新",
     "必须加 WHERE 限定范围；UPDATE/DELETE 前先 SELECT 验证影响行数")

rule("ORDER_BY_RAND", "high",
     r"order\s+by\s+rand\s*\(", "ORDER BY RAND() 对每行求随机值再排序，O(n log n) 且无法索引",
     "先算随机主键再 JOIN，或限制候选集后再随机")

rule("HUGE_LIMIT", "medium",
     r"limit\s+\d{6,}", "LIMIT 百万级会先物化再截断，深分页性能差",
     "用游标分页(keyset pagination)：WHERE id > last_id ORDER BY id LIMIT n")

rule("OR_BLOCKING_INDEX", "medium",
     r"\bor\b", "OR 连接的条件可能阻止单索引命中（需多列或 index merge）",
     "评估拆成 UNION ALL，或建立覆盖 OR 的复合索引")

rule("NESTED_IN_SUBQUERY", "medium",
     r"in\s*\(\s*select", "IN (SELECT ...) 子查询在某些优化器下退化为相关子查询",
     "改 JOIN 或 EXISTS；确保被驱动表有索引")

def analyze(sql):
    findings = []
    for rid, sev, pat, problem, suggestion in RULES:
        m = pat.search(sql)
        if m:
            findings.append({
                "id": rid, "severity": sev,
                "match": m.group(0).strip(),
                "problem": problem, "suggestion": suggestion,
            })
    order = {"high": 0, "medium": 1, "low": 2}
    findings.sort(key=lambda f: order[f["severity"]])
    return findings

if __name__ == "__main__":
    if len(sys.argv) > 1:
        with open(sys.argv[1], encoding="utf-8") as f:
            sql = f.read()
    else:
        sql = sys.stdin.read()
    print(json.dumps(
        {"sql_preview": sql.strip()[:120], "findings": analyze(sql)},
        ensure_ascii=False, indent=2))
```

**预期结果**：在终端单独跑 `echo "SELECT * FROM t WHERE name LIKE '%ab' ORDER BY RAND()" | python3 scripts/sql_doctor.py`，应输出三条 high/medium 级别发现。这一步是给脚本本身做单元自测——脚本对了，Skill 才稳。

**常见问题**：

- 报 `SyntaxError: invalid syntax`？检查 Python 版本是否 ≥3.8，f-string 是 3.6+ 特性。
- `MISSING_WHERE` 误报？规则要求 SQL 以 select/update/delete 开头，如果你的文件首行是注释 `--`，先 `strip()` 掉注释行即可。

### 第 6 步：安装与发现——让 Claude 看见它

不需要"安装命令"。文件落盘即安装。重启或新开一个 Claude Code 会话后，输入：

```
/skill
```

应能在列表里看到 `sql-slow-query-doctor`。看不见？排查顺序：①目录是不是放在了 `.claude/skills/` 正下方（别多套一层）；②`SKILL.md` 文件名大小写没拼错；③frontmatter 的 `---` 围栏前后没有多余空行。

### 第 7 步：两种调用方式，何时用哪种

**方式 A：显式调用（最可控）**

直接喊它的名字，或在对话里打 `/sql-slow-query-doctor`：

```
> 帮我诊断这条：SELECT * FROM orders WHERE DATE(created_at) = '2026-07-01'
```

**方式 B：自动触发（最省心）**

正因为第 3 步把 `description` 写满了同义词，下面这种没点名 Skill 的说法也会被自动接住：

```
> 这条查询在线上很慢，帮我看看怎么优化：SELECT * FROM orders WHERE DATE(created_at) = '2026-07-01'
```

两种方式各有所长：调试期用显式调用，确认稳定后靠自动触发解放双手。如果你发现它**该触发却没触发**，几乎都是 `description` 写得不到位——回到第 3 步加关键词。

### 第 8 步：用固定用例回归测试

造 Skill 最怕"时灵时不灵"。给它准备一个**固定的坏查询用例集**当回归基线：

```sql
-- /tmp/bad_queries.sql
SELECT * FROM users;
UPDATE orders SET status=1;
SELECT id FROM t WHERE name LIKE '%abc' ORDER BY RAND() LIMIT 1000000;
SELECT * FROM a WHERE id IN (SELECT id FROM b WHERE x=1);
```

跑 `python3 scripts/sql_doctor.py /tmp/bad_queries.sql`，记录每条命中的规则数和严重度。以后每次改 `SKILL.md` 或脚本，都拿这份用例回归——**发现条数只增不减、不回归**，Skill 才算稳。

## 实战案例：一条"线上报警"的诊断全过程

假设线上 DB 报警，同事甩来一条慢查询：

```sql
SELECT * FROM user_logs
WHERE DATE(created_at) = '2026-07-01'
  AND message LIKE '%error%'
ORDER BY RAND()
LIMIT 5000000;
```

你只需要在 Claude Code 里贴上去并说"帮我看看这条查询"。Skill 自动接管，跑脚本，得到按严重度排序的报告（节选）：

| 严重度 | 规则 | 问题片段 | 建议 |
|---|---|---|---|
| 🔴 high | FUNC_ON_INDEX | `DATE(created_at) =` | 改写为 `created_at >= '2026-07-01' AND created_at < '2026-07-02'` |
| 🔴 high | LEADING_WILDCARD_LIKE | `LIKE '%error%'` | 上全文索引，或限制日志级别前缀后再匹配 |
| 🔴 high | ORDER_BY_RAND | `ORDER BY RAND()` | 先 `MAX(id)` 算范围，再随机抽主键 |
| 🟡 medium | SELECT_STAR | `SELECT *` | 改为具体列 |
| 🟡 medium | HUGE_LIMIT | `LIMIT 5000000` | keyset 分页 |

末尾还会给出建议索引，例如 `CREATE INDEX ix_logs_created_msg ON user_logs(created_at, level)`。**全程模型没"编"任何结论**——每条都来自脚本的确定性匹配，模型只做了"JSON→人类报告"的翻译。这就是把 Skill 做扎实的回报：可复现、可回归、可问责。

## 工作流程图

```mermaid
flowchart TD
    U[用户贴慢查询 / 说'帮我优化'] --> M{Claude 判断相关性}
    M -->|description 命中| L[读入 SKILL.md 正文]
    M -->|不命中| X[不触发, 当普通对话处理]
    L --> W[把 SQL 写入临时文件]
    W --> R[Bash 调用 sql_doctor.py]
    R --> J[脚本输出 JSON: findings+severity]
    J --> F[Claude 翻译成排序报告+索引建议]
    F --> O[输出诊断, 不碰项目代码]
    subgraph 渐进披露
      FM[frontmatter: 常驻, 便宜] -.按需.-> BD[SKILL.md 正文]
      BD -.按需.-> SC[scripts/sql_doctor.py]
      BD -.按需.-> RF[references/rules.md]
    end
```

## 延伸阅读

- 把规则集做大后，可把 `references/rules.md` 拆成"基础规则"和"进阶规则"，让正文根据查询复杂度决定加载哪份——这是渐进披露的进阶玩法。
- 想让 Skill 跨项目复用，把目录从 `.claude/skills/` 移到 `~/.claude/skills/`，它就成了个人全局工具。
- 想发布给团队，把 Skill 目录连同脚本一起提交进仓库的 `.claude/`，队友 pull 下来即可用。

## 训练营适用性评估

- **适合训练营类型**：AI 工程实战营、后端/DBA 提效营、企业内部"造工具"工作坊。
- **教学时长**：约 **2.5–3 小时**（讲解 45 分钟 + 跟做 90 分钟 + 调试与回归测试 30 分钟）。
- **难度**：中等。门槛在于 frontmatter 的 `description` 调优和"脚本负责确定性、模型负责调度"的分工意识，需要学员动手踩几次"没触发"的坑才能内化。
- **可拓展作业**：让学员各自挑一个痛点（正则校验、配置漂移检测、日志聚合），套用同一套四件套结构再造一个 Skill 并互相触发测试。

## 参考来源

1. [Extend Claude with skills — Claude Code 官方文档](https://code.claude.com/docs/en/skills) — Skills 官方规范、目录约定与渐进披露原理
2. [How to create custom skills — Claude 帮助中心](https://support.claude.com/en/articles/12512198-how-to-create-custom-skills) — 创建自定义 Skill 的官方步骤指引
3. [Claude Code Skill Frontmatter: Every YAML Option Explained](https://allahabadi.dev/blogs/ai/claude-code-skills-frontmatter-complete-guide/) — frontmatter 全字段（name/description/allowed-tools/disable-model-invocation 等）逐项解析
4. [Agent Skills in the SDK — Claude Code 官方文档](https://code.claude.com/docs/en/agent-sdk/skills) — Agent SDK 中 Skill 的自动触发与调用机制
5. [Claude Code Skills: How to Set Them Up and Use Them](https://dev.to/padawanabhi/claude-code-skills-how-to-set-them-up-and-use-them-1chm) — Skill 目录结构、安装与 `/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)
