本教程面向已经能在终端里跑通 Claude Code、想从"用别人写好的东西"升级到"自己造能力"的开发者。你将从一个空目录开始,完整造出一个 sql-slow-query-doctor(SQL 慢查询诊断)Skill:把一段 SELECT/UPDATE 语句丢给它,它自动揪出 SELECT *、前导通配 LIKE '%xx'、列上套函数、缺 WHERE、ORDER BY RAND() 等性能杀手,给出按严重度排序的改写建议与索引方案。过程中你会吃透 Skill 的四件套——frontmatter、正文指令、挂载脚本、渐进披露——并掌握"安装→发现→手动调用→自动触发→测试调优"的完整闭环。
claude 可用)~/skill-lab/,里面放几个 .sql 样例很多人把 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 团队共享。
命名遵循三点:全小写、kebab-case、动词或名词短语。sql-slow-query-doctor 比 sql_helper 或 DB工具 都好——前者一眼能猜出能力边界。目录名就是 Skill 名,别用空格、别用中文。
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
frontmatter 是 Skill 能否被自动触发的命根子。Claude 决定要不要读你的正文,几乎完全看 description。先看一份合格的:
---
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 要的是"用户一问就自动接手",所以不设这个字段。正文不需要长,关键是把决策点和操作步骤写死。原则:能用脚本确定的事绝不靠模型"想",模型只负责调度和解读。
# 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 当命令行参数传,引号和换行分分钟让脚本解析出错。让脚本读文件,是最省心的接口。
这是 Skill 区别于"纯提示词"的关键:把可重复、可验证的逻辑下沉到脚本,模型负责调用,脚本负责算。下面这份分析器是纯标准库,复制即可运行:
#!/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() 掉注释行即可。不需要"安装命令"。文件落盘即安装。重启或新开一个 Claude Code 会话后,输入:
/skill
应能在列表里看到 sql-slow-query-doctor。看不见?排查顺序:①目录是不是放在了 .claude/skills/ 正下方(别多套一层);②SKILL.md 文件名大小写没拼错;③frontmatter 的 --- 围栏前后没有多余空行。
方式 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 步加关键词。
造 Skill 最怕"时灵时不灵"。给它准备一个固定的坏查询用例集当回归基线:
-- /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 报警,同事甩来一条慢查询:
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 做扎实的回报:可复现、可回归、可问责。
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 拆成"基础规则"和"进阶规则",让正文根据查询复杂度决定加载哪份——这是渐进披露的进阶玩法。.claude/skills/ 移到 ~/.claude/skills/,它就成了个人全局工具。.claude/,队友 pull 下来即可用。description 调优和"脚本负责确定性、模型负责调度"的分工意识,需要学员动手踩几次"没触发"的坑才能内化。/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 等场景全覆盖。
