首页 › tutorials › 造SQL慢查询诊断Skill

概述

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

前置条件

正文:八步造出一个真实可用的 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 名,别用空格、别用中文。

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。先看一份合格的:

---
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
---

逐字段拆解:

可选但重要的字段:

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

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

# 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 区别于"纯提示词"的关键:把可重复、可验证的逻辑下沉到脚本,模型负责调用,脚本负责算。下面这份分析器是纯标准库,复制即可运行:

#!/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 才稳。

常见问题:

第 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 最怕"时灵时不灵"。给它准备一个固定的坏查询用例集当回归基线:

-- /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

延伸阅读

训练营适用性评估

参考来源

  1. Extend Claude with skills — Claude Code 官方文档 — Skills 官方规范、目录约定与渐进披露原理
  2. How to create custom skills — Claude 帮助中心 — 创建自定义 Skill 的官方步骤指引
  3. Claude Code Skill Frontmatter: Every YAML Option Explained — frontmatter 全字段(name/description/allowed-tools/disable-model-invocation 等)逐项解析
  4. Agent Skills in the SDK — Claude Code 官方文档 — Agent SDK 中 Skill 的自动触发与调用机制
  5. Claude Code Skills: How to Set Them Up and Use Them — 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学通信知识星球海报-扫码加入