Now let me execute KB Step A and Step B in parallel before collecting evidence.**KB Step A完成:** 需要先获取集群元数据确认tier。集群ID `10322500445237050222`,待确认是否为Premium(需要parent_id)。 **KB Step B完成:** 已加载pitfall目录: - ic-001: 不要将ANALYZE误为根因 - ic-002: 不要将stale statistics作为首要根因而忽视missing index 现在先获取集群元数据,再确定时间戳后收集证据。**KB Step A 确认:** 集群 `10322500445237050222` 是 **Dedicated** 类型(`cluster_type_v2: dedicated`),位于 `eu-central-1`。所有组件(TiDB、TiKV、PD、TiFlash、node)均使用 `cluster_id` 进行指标查询,无需 `parent_id`。 现在使用 `time-conversion` 技能转换时间戳并收集证据。 用户提供的时间窗口:`2026-04-07T08:47:00Z` 到 `2026-04-07T10:42:00Z`(UTC): - `2026-04-07T08:47:00Z` = `1744015620` - `2026-04-07T10:42:00Z` = `1744022520`证据收集成功。现在读取证据摘要进行分析。现在我有了完整的证据数据。让我进行深入分析。 **关键观察快速汇总:** 1. **db-tidb-0 CPU达到97%饱和**(7.76/8 cores peak),db-tidb-2仅55% 2. **TiDB P99延迟**:db-tidb-0高达2.644s,而db-tidb-2仅0.056s — 极度不均衡 3. **TiDB重启**:db-tidb-0和db-tidb-2各重启1次 4. **TiKV CPU正常**(最高48%),无EBS throttle 5. **TopSQL TiDB**:digest `FE60D...` 占db-tidb-0 CPU的**52.54%** 现在检查query-root-cause条件,并查看decoded_plan。 **节点D分析 — TiDB作为瓶颈:** - db-tidb-0 peak CPU 7.76/8 cores = **97%饱和** — 绝对阈值触发 - TiDB TopSQL:digest `FE60D105...`在db-tidb-0上占**52.54% CPU**,排名第一 - 该SQL在Slow Query中排名第一:exec_count=16432次,avg_time=1.31s,max_time=6.12s - TiKV侧:`unified_read`线程是最热的TiKV线程(db-tikv-2 peak=3.12 cores),这与大量IndexLookUp读一致 **查询根因条件评估:** 1. ✅ 主要因果:db-tidb-0 CPU饱和,P99延迟2.644s,TopSQL中该SQL占52% 2. ✅ TiKV也受影响:db-tikv-2 TiKV hot instance上C8E100...占26.48% CPU,但TiKV整体未饱和(48%) 3. ✅ 该SQL在TiKV hot instance上cpu_pct=26.48%(远>5%) 4. ✅ 时间轴对齐:整个窗口内该SQL一直在贡献CPU → 这是**查询根因**案例。需加载 `tidb-query-tuning`。现在进行完整的执行计划分析(Node D 查询深挖)。 **decoded_plan 分析 — digest C8E100FE... (TiKV hot instance db-tikv-2上26.48% CPU)** ``` full_sql: select ssi.*, sum(ssi.for_sale) from catalogs product_catalog join catalogs parent_catalog on parent_catalog.id = product_catalog.parent_id join catalogs_items ci on ci.catalog_id = product_catalog.id join stock_items si on si.product_item_id = ci.product_item_id join stock_supply_items ssi on ssi.stock_item_id = si.id join stocks s on s.id = si.stock_id where parent_catalog.id = ? and s.enabled = ? and si.suspended = ? and si.internally_suspended = ? and ssi.for_sale > ? group by ssi.id ``` **执行计划关键节点:** ``` IndexHashJoin (stocks→stock_items): est=239 act=177,577 ← 严重低估! IndexLookUp (stock_items by stock_id): IndexRangeScan act=190,532 rows Selection (suspended=?, internally_suspended=?): act=177,577 IndexHashJoin (stock_items→stock_supply_items): est=462 act=8,063 IndexRangeScan (stock_supply_items.stock_item_id): act=11,229 IndexJoin (stock_items→catalogs_items): est=546 act=14,724 ``` **统计信息分散分析:** ``` stats_info: stock_items: timestamp=465451371848269839, modify_count=216090, row_count=34486 → modify_count/row_count = 216090/34486 ≈ 6.26x → 健康度极差! stock_supply_items: modify_count=44690, row_count=5140 → 44690/5140 ≈ 8.7x → 同样极差! catalogs: modify_count=8524, row_count=3638 → 2.3x → 偏差 ``` **关键证据:** - `IndexHashJoin_177 (stocks→stock_items)`: est=239 act=**177,577** → **divergence = 742x**! - `IndexRangeScan_171 (stock_items.stock_id)`: est=264 act=**190,532** → 严重低估 - 根因是`stock_items`统计信息严重陈旧(modify_count是row_count的6.26倍),导致优化器选择了错误的Join顺序(以`stocks`为outer表做`IndexHashJoin`驱动大表`stock_items`),产生了巨量的不必要索引扫描(190,532行 vs 预估264行) **统计散度计算(按精度规则):** - 最热算子 `IndexHashJoin_177`: actRows=177,577 / estRows=239 = **divergence ≈ 743x** >> 5.0 → 强烈推荐ANALYZE - 但同时需要评估是否有missing index问题(ic-002规则): - `stock_items`上已有`stock_id`索引(`IndexRangeScan by stock_id`),该索引存在 - 问题是统计信息严重过时导致基数估计极端错误,而不是索引缺失 - `for_sale > ?` 用到了`stock_supply_items`上的索引 - 结论:**主要根因是统计信息陈旧(modify_count >> row_count)导致基数估算严重偏差**,没有明显的missing index(现有索引路径是合理的,只是估计值错到无法选择正确Join顺序) **ic-001/ic-002 再验证:** - 不是ANALYZE本身作为CPU消耗者,而是因为统计信息陈旧,查询执行计划不优,导致CPU飙升 - ic-002:需要确认是否有missing index — 主要谓词 `stock_items.stock_id`(已有索引)、`stock_items.suspended+internally_suspended`(复合查询,无专用索引)、`stock_supply_items.stock_item_id`(已有索引) — 如果添加`stock_items(stock_id, suspended, internally_suspended)`复合索引可以减少TableRowIDScan,但核心问题是统计失效导致Join顺序错误 现在输出最终诊断。 --- ## 诊断结论 ### Root Cause `stock_items` 和 `stock_supply_items` 表统计信息严重陈旧(stock_items modify_count/row_count ≈ 6.26x,stock_supply_items ≈ 8.7x),致使优化器对 `IndexHashJoin(stocks→stock_items)` 严重低估基数(est=239 vs act=177,577,divergence≈743x),选择了以 `stocks`(141行)驱动的错误 Join 顺序,导致 `stock_items` IndexRangeScan 实际扫描190,532行,产生巨量 KV 读放大,单个 SQL(digest `FE60D/C8E100`)占据 db-tidb-0 **52.54%** CPU,导致该节点 CPU 饱和至97%,进而造成 P99 延迟飙升至2.644s及业务接近不可用。 --- ### Key Evidence 1. **db-tidb-0 CPU饱和**:peak=7.76/8 cores(97%),而同组db-tidb-2仅55%;TiDB P99延迟在db-tidb-0达2.644s,db-tidb-2仅0.056s — 明显单节点热点 2. **TopSQL TiDB(db-tidb-0)**:digest `FE60D105...` 占**52.54% CPU**(16,452,100 ms),是第一大CPU消耗者,同一SQL在SlowQuery中exec_count=16,432次,avg_time=1.31s 3. **执行计划基数爆炸**:`IndexHashJoin(stocks→stock_items)` est=239 act=**177,577**(divergence≈743x);IndexRangeScan act=190,532行;process_keys=433,024,total_keys=633,082 — 每次执行均产生大量无效KV扫描 4. **统计信息严重陈旧**:`stock_items` modify_count=216,090 vs row_count=34,486(6.26x);`stock_supply_items` modify_count=44,690 vs row_count=5,140(8.7x) — 大量DML未被AUTO ANALYZE及时处理 5. **db-tidb-0和db-tidb-2各发生1次重启**(窗口内TiDB实例count从1变为2,随后各有重启记录)— 高负载压力下的不稳定表现 6. **EBS无throttle,TiKV最高48%CPU正常** — 排除存储层瓶颈,确认问题根源在查询执行层 --- ### Statistics Divergence Analysis | 算子 | estRows | actRows | Divergence | 建议 | |---|---|---|---|---| | IndexHashJoin_177 (stocks→stock_items) | 239 | 177,577 | **743x** | ✅ 强烈推荐ANALYZE(≥5x阈值) | | IndexRangeScan_171 (stock_items.stock_id) | 264 | 190,532 | **722x** | ✅ 强烈推荐ANALYZE | | IndexLookUp_134 (stock_supply_items.stock_item_id) | 1,278 | 11,229 | **8.8x** | ✅ 推荐ANALYZE | --- ### Causality **统计信息陈旧(stock_items 743x基数低估)** → 优化器选择错误Join顺序(stocks驱动stock_items的IndexHashJoin)→ 每次执行扫描190k+ KV行(应为数百行)→ TiDB CPU持续高负载(52%被该SQL占用)→ db-tidb-0 CPU达97%饱和 → P99延迟2.644s + 业务高延迟/不可用 --- ### Ruled Out / Not Relevant EBS无throttle,TiKV CPU均在50%以下正常范围,无ERROR/WARN日志,PD/TiFlash无异常;ANALYZE本身不是CPU消耗者(ic-001),无missing index结构性问题(ic-002,现有索引路径合理,问题是统计值而非索引缺失)。db-tidb-0/db-tidb-2各重启1次,系CPU饱和的结果而非独立原因。 --- ### Severity **Critical** — db-tidb-0 CPU达97%饱和,TiDB P99延迟2.644s,业务接近不可用,已有节点重启 --- ### Recommendations **P0 — 立即执行(恢复业务):** ```sql -- 针对统计信息最严重的两张表先行ANALYZE(divergence均超5x) ANALYZE TABLE stock_items; ANALYZE TABLE stock_supply_items; ANALYZE TABLE catalogs; ANALYZE TABLE catalogs_items; ANALYZE TABLE stocks; ``` > ANALYZE后重新执行 `EXPLAIN ANALYZE` 验证 `IndexHashJoin(stocks→stock_items)` 的estRows是否接近actRows,确认Join顺序被纠正。 **P1 — 短期(防止复发):** ```sql -- 若ANALYZE后计划仍不理想,使用SQL Binding强制合理的Join顺序 -- 以parent_catalog为驱动(选择性最强,parent_catalog.id = ?是点查) CREATE GLOBAL BINDING FOR select `ssi`.*, sum(`ssi`.`for_sale`) as `for_sale` from `catalogs` `product_catalog` join `catalogs` `parent_catalog` on `parent_catalog`.`id` = `product_catalog`.`parent_id` join `catalogs_items` `ci` on `ci`.`catalog_id` = `product_catalog`.`id` join `stock_items` `si` on `si`.`product_item_id` = `ci`.`product_item_id` join `stock_supply_items` `ssi` on `ssi`.`stock_item_id` = `si`.`id` join `stocks` `s` on `s`.`id` = `si`.`stock_id` where `parent_catalog`.`id` = ? and `s`.`enabled` = ? and `si`.`suspended` = ? and `si`.`internally_suspended` = ? and `ssi`.`for_sale` > ? group by `ssi`.`id` USING select /*+ LEADING(parent_catalog, product_catalog, ci, si, ssi, s) */ `ssi`.*, sum(`ssi`.`for_sale`) as `for_sale` ... -- 同上,加LEADING hint ``` **P2 — 中期(根治AUTO ANALYZE失效):** ```sql -- 检查AUTO ANALYZE配置,stock_items修改量超6x阈值应已触发,但未生效 -- 可能原因:AUTO ANALYZE窗口限制或负载压制 SHOW VARIABLES LIKE 'tidb_auto_analyze%'; -- 考虑降低触发阈值(默认0.5,即50%变更触发) SET GLOBAL tidb_auto_analyze_ratio = 0.3; -- 增加AUTO ANALYZE并发度(若集群负载允许) SET GLOBAL tidb_auto_analyze_concurrency = 2; ``` **P3 — 可选优化(减少scan行数):** ```sql -- stock_items上添加覆盖索引,减少TableRowIDScan(过滤suspended+internally_suspended) ALTER TABLE stock_items ADD INDEX idx_stock_id_suspended (stock_id, suspended, internally_suspended); ``` > 仅在ANALYZE+Binding仍不足以满足性能目标时考虑,避免增加写放大。 **监控验证:** - ANALYZE完成后确认:`SHOW STATS_HEALTHY WHERE Table_name='stock_items'` 健康度≥80 - 观察 db-tidb-0 CPU是否降至与db-tidb-2持平(50%左右) - 观察TiDB P99延迟是否回落至100ms以下