慢 SQL 诊断与自动调优
概述
本 Skill 提供 TCHouse-C(ClickHouse)集群的慢 SQL 诊断与自动调优能力,包含三个子能力:
- 慢查询 Top N 检索与趋势分析:检索慢查询日志,分析趋势判断是突发还是持续恶化
- 执行计划分析与瓶颈定位:对慢 SQL 执行 EXPLAIN,识别全表扫描/JOIN 不优/索引缺失等问题
- SQL 改写与索引优化方案生成:生成 SQL 改写、跳数索引、排序键调整、物化视图等优化建议
依赖与运行环境
本 Skill 的所有调用通过 MCP Tool 完成(云 API 类工具由平台封装为 MCP Tool,Agent 直接调用工具名即可)。
依赖工具清单:
| # | Tool 名称 | 能力定位 | 参考文档 |
|---|---|---|---|
| 1 | TCHouseCDescribeInstance | 集群信息获取 | 参考 |
| 2 | TCHouseCDescribeSlowQueryRecords | 慢查询明细检索 | 参考 |
| 3 | TCHouseCDescribeSlowQueryTrend | 慢查询趋势分析 | 参考 |
| 4 | TCHouseCDescribeRunningQuery | 正在运行的查询 | 参考 |
| 5 | TCHouseCDescribeCkSqlApis | SQL 执行计划获取 | 参考 |
| 6 | TCHouseCDescribeTableSchema | 表结构/索引信息 | 参考 |
| 7 | TCHouseCDescribeClusterConfigs | 集群配置参数 | 参考 |
| 8 | ask_user | 向用户询问确认(WorkBuddy 中为 AskUserQuestion) | — |
凭证 / 环境变量
instance_id:从会话 context 的 X-Context header 自动注入region_id:从会话 context 的 X-Context header 自动注入(可能是RegionId数字、Region字符串或中文地域名)- 若以上参数缺失,通过
ask_user(WorkBuddy 中为AskUserQuestion)询问用户
⚠️ 地域参数强制规则:本 Skill 依赖的全部工具(
TCHouseCXxx系)都只接受Region字符串(如ap-guangzhou)。任何工具调用前都必须先按 地域映射表 将上下文中的地域信息(无论是中文名、英文串还是RegionId数字)统一转为Region字符串后再传入,禁止凭记忆填写。详见 工具传参形式速查。
💡 多平台兼容说明:本文档中所有提到的
ask_user工具,在 WorkBuddy 平台中对应为AskUserQuestion。后文不再重复标注。
核心工作流
步骤 0:参数确认
必需参数:
instance_id(集群 ID)region_id(地域)
可选参数(从用户问题中提取,缺失时使用默认值,不自行假设):
- 时间范围:未指定 → 默认最近 7 天
- 慢查询阈值:未指定 → 默认 500ms
- Top N 条数:未指定 → 默认 10
判断逻辑:
- ✅ 参数齐全 → 强制按 地域映射表 将地域信息统一转为
Region字符串(任何输入形式都要过这一步:中文名、英文串、数字 ID 都不例外),转换后进入步骤 1 - ❌
instance_id或region_id缺失 → 调用ask_user询问 - ❌ 地域信息在映射表中匹配不到(或大区模糊,如"华南地区")→ 调用
ask_user确认后再转换
步骤 1:确认集群信息
调用 TCHouseCDescribeInstance 获取集群基本信息。
判断逻辑:
- ✅ 集群状态为
Serving→ 进入步骤 2 - ❌ 集群状态为
Init/Modify→ 告知用户集群当前不可用,建议等待恢复后重试 - ❌ 集群状态为
Deleted/Deleting→ 告知用户集群已下线,终止诊断 - ❌ 调用失败(AuthFailure)→ 报告错误,提示检查权限
- ❌ 调用失败(ResourceNotFound)→ 检查 instance_id 格式(应为
cdwch-前缀),格式错则修正重试,格式对则请用户确认
记录信息:ClickHouse 版本号(影响 EXPLAIN 支持)、节点规格和数量。
步骤 2:获取慢查询数据
调用 TCHouseCDescribeSlowQueryRecords 获取慢 SQL 明细列表。
参数确定策略:
- 时间范围:根据用户描述("最近一周" → 7天,"昨天" → 昨天0点到今天0点)
- 慢查询阈值(QueryDurationMs):用户指定则按用户要求,未指定默认 500ms
- PageSize:默认 10,用户指定条数则按用户要求
- 排序:
SortColumn: query_duration_ms+SortOrder: DESC(按耗时降序)
判断逻辑:
- ✅ 返回有数据 → 进入步骤 3
- ❌ 返回为空 → 告知用户,建议:1) 扩大时间范围 2) 降低阈值(如 500ms → 100ms)
- ❌ 返回错误提示慢查询日志未开启 → 提供开启步骤指引(控制台 → 集群详情 → 参数配置 → 开启 slow_log),告知需等待数据积累
- ❌ 调用超时 → 等 3 秒重试,最多 3 次;仍失败则告知用户服务暂时不可用
辅助步骤(可选):调用 TCHouseCDescribeSlowQueryTrend 获取趋势数据,判断慢查询是突发性还是持续恶化。
⚠️ 参数命名注意:
TCHouseCDescribeSlowQueryTrend的集群 ID 参数名为InstanceID(大写 D),与其他工具的InstanceId(小写 d)不同,传参时务必注意拼写。
⚠️ 调用策略:该接口按分钟级粒度返回数据点,单次查询时间范围过大会导致返回数据超出 token 上限。请根据步骤 2 中
TotalCount(慢查询总条数)和查询天数,计算日均慢查询数 = TotalCount ÷ 天数,然后按以下策略调用:
日均慢查询数 策略 理由 ≤ 50 条/天 直接查整个时间段(如 7 天一次性查) 数据稀疏,分钟级数据点大部分为 0,返回总量可控 50~500 条/天 采样 3 天:最近 1 天 + 中间 1 天 + 最早 1 天 3 个采样点足够判断趋势走向(上升/下降/平稳),避免调用过多 > 500 条/天 只查最近 1 天 数据密集,1 天内的分钟级趋势已足够判断模式(突发/持续),超过 1 天必超 token 时间范围 ≤ 1 天时:无论日均数量多少,直接查询即可。
示例:用户查 7 天,TotalCount=210 → 日均 30 条 → 直接查 7 天整段;TotalCount=2100 → 日均 300 条 → 采样第 1、4、7 天各查 1 次。
步骤 3:检查当前运行查询(条件触发)
触发条件:用户描述的是"正在发生"的问题(如"现在查询很慢"、"当前有查询卡住")。
调用 TCHouseCDescribeRunningQuery 查看当前长耗时 SQL,辅助判断锁等待、资源争抢、死循环查询。
判断逻辑:
- ✅ 有长耗时查询 → 纳入分析范围
- ✅ 无异常 → 继续步骤 3.5
- ❌ 调用失败 → 跳过此步骤,基于历史慢查询数据继续分析
步骤 3.5:SQL 模式归一化与去重
对步骤 2 返回的 Top N 慢查询记录进行归一化去重,避免对同一 SQL 模式重复分析。
归一化规则(按顺序应用):
- 将字符串字面量替换为
'?'(如WHERE name = 'Alice'→WHERE name = '?') - 将数字字面量替换为
?(如LIMIT 100→LIMIT ?,id = 12345→id = ?) - 将
IN (...)列表替换为IN (?)(如IN (1,2,3)→IN (?)) - 去除多余空白、统一为单空格
- 转为小写后比较
去重与聚合:
- 归一化后 SQL 文本相同的记录归为同一SQL 模式
- 每个模式记录:出现次数、最大耗时、平均耗时、最近一次执行时间
- 按最大耗时降序排列去重后的模式列表
判断逻辑:
- ✅ 去重后有 ≥ 3 个不同模式 → 取前 3-5 个模式进入步骤 4
- ⚠️ 去重后只有 1-2 个模式(Top N 几乎全是同一条 SQL)→ 执行以下补充策略:
- 记录该高频模式的执行频次(作为独立优化维度:频次高 × 耗时高 = 优先级最高)
- 调用
TCHouseCDescribeSlowQueryRecords翻页(PageNum + 1)或降低 QueryDurationMs 阈值,尝试获取更多不同 SQL 模式 - 最多补充翻页 2 次(受频率控制约束),将新发现的不同模式纳入分析
- ❌ 翻页后仍只有 1 个模式 → 只分析该模式,但在报告中重点标注其高频特征
输出:去重后的 SQL 模式列表(含频次统计),供步骤 4 逐一分析。
步骤 4:逐条分析慢 SQL
对步骤 3.5 输出的去重后 SQL 模式列表中的每个模式(建议重点分析前 3-5 个):
💡 并行化提示:对每个 SQL 模式,4.1(EXPLAIN)和 4.2(表结构)之间无依赖关系,可以并行调用以提高效率。同时步骤 5(集群配置获取)也可与步骤 4 并行启动。
4.1 获取执行计划:
调用
TCHouseCDescribeCkSqlApis,使用PLAN类型✅ 成功 → 进入 4.3
❌ 版本不支持(< 20.6)→ 标记“版本不支持 EXPLAIN”,基于 SQL 文本和表结构给出有限建议
❌ SQL 语法错误/表已删除 → 跳过该条,继续下一条
❌ 执行超时 → 标记“执行计划获取超时”,跳过继续,报告中注明建议用户简化后重试 4.2 获取表结构:
从 SQL 中提取涉及的表名,调用
TCHouseCDescribeTableSchema获取建表 DDL✅ 成功 → 进入 4.3
❌ 表已删除/权限不足 → 跳过表结构分析,基于执行计划给出有限建议
4.3 瓶颈识别:
- 按 分析框架 识别问题类型
- 生成对应优化建议,详见 优化方案模板
步骤 5:检查集群配置(默认执行)
💡 执行策略:本步骤为默认执行,与步骤 4 并行启动。理由:配置获取零依赖、成本低(单次 API 调用)、可在 EXPLAIN/表结构获取失败时提供间接诊断线索。
调用 TCHouseCDescribeClusterConfigs 获取集群配置文件列表。
⚠️ 返回格式说明:该接口返回的是
ClusterConfList数组,每个元素包含FileName(文件名)和FileConf(XML 格式的配置文件全文)。不是结构化键值对,需要从 XML 文本中提取目标配置项。
XML 解析步骤:
定位目标文件:
- 查询级配置(内存/超时/并发)→ 从
FileName = "users.xml"的FileConf中提取 - 服务端配置(连接数/缓存/MergeTree)→ 从
FileName = "config.xml"的FileConf中提取 - 集群拓扑(分片/副本)→ 从
FileName = "metrika.xml"的FileConf中提取
- 查询级配置(内存/超时/并发)→ 从
从 XML 文本中提取关键配置项(使用正则或 XML 标签匹配):
users.xml(
<profiles><default>节点下):<max_memory_usage>— 单次查询内存上限(字节)<max_threads>— 查询并发线程数<max_execution_time>— 查询超时时间(秒)<join_algorithm>— JOIN 算法(hash/partial_merge/auto)<max_bytes_before_external_sort>— 外部排序阈值<max_bytes_before_external_group_by>— 外部 GROUP BY 阈值
config.xml(
<yandex>根节点下):<max_concurrent_queries>— 最大并发查询数<max_connections>— 最大连接数<uncompressed_cache_size>— 未压缩数据缓存<mark_cache_size>— Mark 缓存大小<merge_tree><parts_to_throw_insert>— parts 过多阈值
提取方式:对
FileConf字符串,按 XML 标签名匹配提取值,例如匹配<max_memory_usage>(\d+)</max_memory_usage>获取数值。若标签不存在则表示使用 ClickHouse 默认值。
判断逻辑:
- ✅ 成功获取并解析出配置值 → 纳入综合分析,与步骤 4 的瓶颈对照判断是否为配置瓶颈
- ⚠️ 目标配置项在 XML 中不存在 → 标注"使用默认值",参考 ClickHouse 官方默认值进行分析
- ❌ 接口调用失败 → 跳过配置分析,基于已有数据给出建议,报告中注明未能获取配置信息
步骤 6:综合分析与生成报告
基于收集到的所有信息,按 输出报告格式 生成诊断报告。
报告必须包含:
- 集群概况(ID/版本/状态)
- 慢查询概览(时间范围/总数/平均耗时)
- 趋势分析(突发 vs 持续)
- 逐条诊断(SQL文本/耗时/瓶颈/优化建议)
- 综合优化建议(按优先级排序)
频率控制
| 限制 | 阈值 | 说明 |
|---|---|---|
| 工具总调用频率 | ≤ 10 次/分钟 | 避免触发平台限流 |
| TCHouseCDescribeCkSqlApis 调用 | ≤ 5 次/轮诊断 | EXPLAIN 不执行查询但有解析开销 |
| TCHouseCDescribeSlowQueryRecords 翻页 | ≤ 3 次/轮 | 避免拉取过多数据 |
超限处理:连续收到 RequestLimitExceeded → 等 5 秒重试,连续 3 次仍失败 → 降低调用频率,告知用户被限流。
错误码与处理策略
| 错误码/场景 | Agent 行为 |
|---|---|
AuthFailure.* |
报告鉴权失败,提示用户检查集群访问权限 |
ResourceNotFound |
检查 ID 格式(cdwch- 前缀);格式错 → 修正重试;格式对 → 请用户确认 |
InvalidParameter.* |
检查参数格式(时间范围、阈值),尝试修正后重试 1 次;无法修正 → 报告具体问题 |
UnsupportedRegion |
该地域未开通 TCHouseC 产品。不重试、不自动切换地域,必须调用 ask_user 让用户确认地域。详见 error-handling.md §1 |
InternalError |
等 3 秒重试,最多 3 次;仍失败 → 报告错误码 + RequestId |
RequestLimitExceeded |
等 5 秒重试;连续 3 次 → 降低频率,告知被限流 |
| EXPLAIN 超时 | 标记该 SQL 为"执行计划获取超时",跳过继续处理其余 SQL |
| 表结构获取失败 | 跳过表结构分析,基于执行计划和 SQL 文本给出有限建议 |
| 集群配置获取失败 | 跳过配置分析,基于已有数据给出建议 |
| 网络超时 | 查询类操作等 3 秒重试,最多 3 次;仍失败 → 告知用户服务暂时不可用 |
| 兜底(未列出错误码) | 报告完整错误信息 + RequestId |
安全规则
- 本 Skill 为纯只读诊断:所有操作均为查询类(Describe/Explain),不涉及写操作,无需用户确认即可执行
- SQL 脱敏:输出报告中的 SQL 文本可能包含敏感数据(表名、字段值),如实展示但不额外暴露
- 凭据安全:不在输出中展示任何凭据信息
- 数据量控制:Top N 不超过 10 条,重点分析前 3-5 条,避免 token 消耗过大
- EXPLAIN 安全:EXPLAIN 本身不执行查询,不会对集群产生负载影响
经验沉淀库
| 经验 | 置信度 | 说明 |
|---|---|---|
| 分区键未命中是最常见的慢查询原因 | ⭐⭐⭐ | WHERE 条件未包含分区键字段(如 toYYYYMM(date)),导致全分区扫描 |
| SELECT * 在宽表上性能极差 | ⭐⭐⭐ | ClickHouse 列式存储,SELECT * 读取所有列文件,应只选需要的列 |
| 大表 JOIN 小表时应将小表放右侧 | ⭐⭐ | ClickHouse 默认将右表加载到内存做 hash join |
| PREWHERE 比 WHERE 更高效 | ⭐⭐ | PREWHERE 先过滤再读取其他列,减少 IO;适用于过滤率高的条件 |
| 跳数索引对低基数列效果有限 | ⭐⭐ | minmax 索引对基数低的列(如 status)裁剪效果差,考虑 bloom_filter |
| IN 子查询应改写为 JOIN | ⭐⭐ | IN (SELECT ...) 可能重复执行子查询,改写为 JOIN 性能更优 |
| _local 表只含当前分片数据 | ⭐⭐ | 表名以 _local 结尾表示本地表,SELECT 结果仅为单分片数据而非全集群数据;需确认是否应查分布式表(去掉 _local 后缀) |