# Postmortem: O11Y-2622673

> Workspace: `ws-1784878020053-9144f262a`
> Date: 2026-07-27
> Reviewer: evolver-bot

## Issue Summary

| Field | Value |
|-------|-------|
| Issue ID | O11Y-2622673 |
| Alert Type | TiDBExpensiveQueryCntGt500AndIncreaseFor10m |
| Cluster ID | 10123293495242577103 |
| Cluster Type | dedicated |
| TiDB Version | unknown |
| Time Window | 2026-07-24 07:12:31 UTC – ongoing (alert not recovered) |

**Issue Description**: 持续增加的 expensive query（单次 >10s）触发告警，用户反馈"搜慢查询时连 SQL 都写不对"，实际根因为 `reviews` 表聚合评分 SQL（`bc6171...` digest）存在多个低效执行计划，其中 `2d7c47cf`（avg 9.1s）和 `1dd1366f`（avg 6.9s）索引选择差，导致大量 skipped keys。

## Actual Root Cause

> Human-confirmed root cause from Jira resolution, oncall handoff, or expert analysis.

告警根因是 `reviews` 表的 FORCE INDEX 聚合评分 SQL（digest `bc6171e8f6cde9f502c94de777fcd4fb7e78df4b671b1715f544f1a824d43a81`）存在多执行计划并存问题：8 个 plan_digest 中，`2d7c47cf` 平均耗时 9.1s（max 106s）、`1dd1366f` 平均耗时 6.9s（max 31s），其 total_keys 远大于 process_keys，说明存在大量无效扫描（索引选择不当）。此外告警期间 `auth` 表 binlog 全表扫（digest `2aaff87040e4...`，avg 28.5s，max 264s）贡献了大量 expensive query。告警触发时间 2026-07-24 07:22:31 UTC，告警未恢复。

## Agent Diagnosis Summary

> What the Agent concluded (final answer from unguided first attempt).

Agent 经过多轮工具调用完成了以下分析：

1. **初期查询 `coupon_histories` digest**（用户反馈中提到的 SQL），确认该 digest 在 7/21~7/24 只有唯一执行计划，plan 未发生变化。
2. **扩展到全天慢查询 Top 10**，正确识别 `bc6171...`（reviews 聚合评分）为扫描量最高的 digest（15.6B process_keys）。
3. **分析 `bc6171...` 的 plan 变化**，发现 8 个 plan_digest 在 7/21 就已并存，告警不是由 plan 切换引起。
4. **按 plan_digest 拆解 scan keys**，识别 `2d7c47cf` 和 `1dd1366f` 为低效 plan（avg 9.1s 和 6.9s，total_keys >> process_keys）。

最终结论方向正确，但诊断过程中存在 epoch 换算错误（差了约 364 天），导致多次空查询和无效工具调用，效率较低。

## Scores

| Dimension | Score (1-5) | Notes |
|-----------|-------------|-------|
| Root Cause Accuracy | 4 | 正确识别了低效 plan digest 和 skipped keys 问题，但未完整分析 auth 表 binlog 全表扫的贡献 |
| Evidence Completeness | 3 | 仅查询了 slow_query_logs，未查 metrics（QPS/CPU/延迟趋势）、Activity Hub、TopSQL 等 |
| Causal Chain | 3 | 识别了多 plan 并存 + 低效索引选择 → 高 scan keys → expensive query，但未量化影响 |
| Actionability | 3 | 指出了低效 plan 方向，但未给出具体的 SQL hint 修改建议或 ANALYZE TABLE 建议 |
| Efficiency | 2 | 30 次工具调用中有 3 次失败（CAST AS INT64 错误），多次因 epoch 换算错误产生空查询，浪费约 8 次 tool call |
| **Overall** | **3.0** | |

## Human Guidance Log

> Every point where the reviewer had to manually steer the Agent.

Agent 在此次诊断中经历了用户的 3 次后续跟进（message 5→6 和 message 7→8 和 message 10→11），但这些是用户追问性质（ask for plan comparison、bc6171 plan breakdown）而非纠错干预。

### Intervention 1: 用户追问执行计划比较

- **Round**: message 5 → message 6
- **User said**: （追问 0d6d80 digest 执行计划对比结论后，用户继续追问告警时间窗口内的查询）
- **Agent's mistake**: Agent 在第一次回答后未主动扩展到告警时间窗口的全貌，需用户追问才继续
- **Correct behavior**: 在确认 plan 未切换后，应主动扩展到告警时间窗口的 Top 查询分析
- **Skill**: `clinic-cli`
- **Deficiency type**: FLOW

### Intervention 2: 用户追问 bc6171 plan 详细

- **Round**: message 7–8 → message 9–11
- **User said**: 要求对 `bc6171...` digest 做 plan 变化分析和 scan keys 拆解
- **Agent's mistake**: Agent 在给出全天 Top 10 后没有自动深入最重要的 digest
- **Correct behavior**: 识别出扫描量最高的 digest 后，应自动进行 plan_digest 级别的细分分析
- **Skill**: `clinic-cli`
- **Deficiency type**: FLOW

## Skill Deficiencies

### D1: Epoch 换算错误导致多次无效查询

- **Type**: TEMPLATE
- **Skill**: `clinic-cli`
- **Current behavior**: Agent 手动计算 epoch 时出错（差了约 364 天），在发现 `time` 字段实际分布后才意识到错误，此间产生了多次空查询
- **Expected behavior**: 应优先使用 time-conversion skill 换算时间，或先查询 `time` 字段分布再设置过滤条件
- **Suggested fix**: 在 clinic-cli SKILL.md 中增加提示："使用 time 字段过滤慢查询前，先执行 `SELECT MIN(time), MAX(time)` 确认实际数据范围，再换算告警时间为 epoch"
- **Priority**: P2-medium
- **Status**: open

### D2: CAST 语法错误（INT64 vs UNSIGNED）浪费工具调用

- **Type**: TEMPLATE
- **Skill**: `clinic-cli`
- **Current behavior**: Agent 在 MySQL 方言的 data proxy 中使用了 `CAST(x AS INT64)`，导致 3 次工具调用失败，才改用正确的 `CAST(x AS UNSIGNED)`
- **Expected behavior**: 应在 clinic-cli SKILL.md 中明确说明 data proxy 使用 MySQL 方言，CAST 语法应使用 MySQL 规范（UNSIGNED、DECIMAL 等）
- **Suggested fix**: 在 data-proxy/SUBSKILL.md 中添加"慢查询字段类型说明"：`process_keys`/`total_keys` 是 STRING 类型，聚合时需 `CAST(x AS UNSIGNED)`，不支持 `AS INT64`
- **Priority**: P2-medium
- **Status**: open

### D3: 诊断范围局限于 slow_query_logs，未查 metrics/TopSQL

- **Type**: FLOW
- **Skill**: `tidb-perf-diagnosis`
- **Current behavior**: 整个诊断仅使用 clinic_cli.py sql 查询慢查询，未查 metrics（CPU、QPS、延迟趋势）、TopSQL、Activity Hub 事件
- **Expected behavior**: expensive query 类告警应同时查看 metrics（负载趋势）和 TopSQL（CPU 归因）以形成完整证据链
- **Suggested fix**: 在 tidb-perf-diagnosis 流程中，expensive query 告警路由到慢查询分析后，应要求同时采集告警时间窗口的关键 metrics（QPS、CPU、P99 延迟）
- **Priority**: P2-medium
- **Status**: open

## Key Queries

### 查慢查询字段时间分布（先确认 epoch 范围）

```sql
SELECT MIN(time) AS min_time, MAX(time) AS max_time, COUNT(*) AS total_rows
FROM clinic_data_proxy.slow_query_logs
WHERE date = '20260724'
```

### 告警窗口内 Top 10 扫描量 digest

```sql
SELECT digest, any_value(query) AS sample_sql,
  COUNT(*) AS exec_count,
  SUM(CAST(process_keys AS UNSIGNED)) AS total_process_keys,
  MAX(CAST(process_keys AS UNSIGNED)) AS max_process_keys,
  SUM(CAST(total_keys AS UNSIGNED)) AS total_total_keys,
  MAX(query_time) AS max_query_time,
  AVG(query_time) AS avg_query_time
FROM clinic_data_proxy.slow_query_logs
WHERE date = '20260724'
  AND time >= 1784877151
  AND time <= 1784878707
GROUP BY digest
ORDER BY total_process_keys DESC
LIMIT 10
```

### 按 plan_digest 拆解 scan keys（定位低效 plan）

```sql
SELECT plan_digest,
  COUNT(*) AS exec_count,
  SUM(CAST(process_keys AS UNSIGNED)) AS total_process_keys,
  AVG(CAST(process_keys AS UNSIGNED)) AS avg_process_keys,
  SUM(CAST(total_keys AS UNSIGNED)) AS total_total_keys,
  MAX(query_time) AS max_query_time,
  AVG(query_time) AS avg_query_time
FROM clinic_data_proxy.slow_query_logs
WHERE date IN ('20260721','20260722','20260723','20260724')
  AND digest = 'bc6171e8f6cde9f502c94de777fcd4fb7e78df4b671b1715f544f1a824d43a81'
GROUP BY plan_digest
ORDER BY total_process_keys DESC
```

## Lessons Learned

- **慢查询 `time` 字段是 epoch 浮点数**，不是 ISO timestamp；在过滤前应先查字段分布范围，避免基于错误 epoch 的空窗口查询。
- **MySQL 方言不支持 `CAST(x AS INT64)`**，应使用 `CAST(x AS UNSIGNED)` 或 `CAST(x AS DECIMAL)`；clinic-cli 的 data proxy 是 MySQL 方言，这一点应在 SUBSKILL.md 中明确。
- **expensive query 类告警应同时看 metrics 和 SlowQuery**：仅看 slow_query_logs 无法量化告警触发时的系统整体压力（CPU/QPS），需要 metrics 数据支撑因果链。
- **多 plan 并存场景的诊断路径**：确认 plan 未切换后，应进一步按 plan_digest 拆解 scan keys，找出低效 plan 及其 index 选择差异，才能给出可执行的优化建议。
