实习期间遇到的第一个Bug:服务实例详情页"慢调用链路"数字和点进去后的总数不一致。排查发现ClickHouse聚合函数sum()受WHERE条件影响导致统计口径变化,最终用CASE WHEN修复。记录完整排查过程和方案排除思路。
一、问题来了
上周,导师给我发了了个"小缺陷":
服务实例详情 → 异常分析页面,"慢调用链路"显示的数字是 A ,点"查看全部错误链路" → "仅看慢调用"后,调用链总数显示的是 B。A ≠ B,但这两个数字应该表示同一个含义。
说实话,刚看到这个问题我有点懵------数字不就是个数吗,怎么还能不一样?
二、排查:F12对比请求参数
排查Bug的第一步,永远是看它传了什么参数。
打开F12 → Network,我分别抓了两次请求:
点"异常"时、点"仅看慢调用"时
发现问题了:
| 操作 | stats 接口 | list 接口 |
|---|---|---|
| 点"异常" | 没传 state | state=error |
| 点"仅看慢调用" | 传了 state=isSlow | state=isSlow |
同一个 stats 接口,不同场景下收到的参数不一样,返回的数字当然不一样。
三、追代码:从Controller追到Repository
沿着调用链一层层追:
xxxxxxxxxxController(接收 Param)→ xxxxxxxxxxxImpl(Service层,直接透传) → xxxxxxxxxRepository → buildTraceWhere() 方法
关键代码在 buildTraceWhere:
java
// 动态拼接 WHERE 条件
if (StringUtils.hasText(param.getState())) {
sql.append(switch (param.getState()) {
case "success" -> " AND isError = 0 AND isSlow = 0";
case "error" -> " AND isError > 0";
case "isSlow" -> " AND isError = 0 AND isSlow > 0"; // ← 问题在这
default -> " AND 1 = 2";
});
}
逻辑很清楚:
- state 为空(没传)→ if 不执行 → 不过滤
- state 有值(传了)→ if 执行 → 按条件过滤
四、根因:聚合函数被WHERE"带偏了"
stats 接口的核心SQL:
sql
SELECT
sum(isError) AS errorCount,
sum(isSlow) AS slowCount -- ← 这个有问题
FROM trace_table
WHERE ...
问题很隐蔽:
| 场景 | WHERE条件 | sum(isSlow) 统计的是 |
|---|---|---|
| 点"异常"(没传state) | 不过滤 | 全量数据中的慢调用 |
| 点"仅看慢调用"(state=isSlow) | isError = 0 |
排除错误后的慢调用 |
聚合函数的统计口径跟着 WHERE 走------这就是根因。
五、方案排除:我纠结的过程
这个问题不大,但方案选择我想了很久。
方案一:去掉 buildTraceWhere 里的 isError = 0
不行。如果去掉,慢调用列表会出现错误调用数据,不符合产品要求------"既是慢调用又是错误的,不属于慢调用"。
方案二:在调用 Repository 前把 state 置为 null
不行。如果置null,buildTraceWhere 的 switch 不走,list 接口的过滤也失效了,"仅看错误"和"仅看慢调用"列表会串数据。
方案三:不改 WHERE,改聚合函数本身
✅ 让聚合函数自己定义统计规则,不依赖 WHERE 过滤。
六、修复:一行代码搞定
最终只改了一行SQL:
sql
-- 修复前
sum(isSlow) AS slowCount
-- 修复后
sum(CASE WHEN isSlow > 0 AND isError = 0 THEN 1 ELSE 0 END) AS slowCount
导师看了后说:ClickHouse 有更简洁的写法------sumIf:
sql
-- ClickHouse 专属语法(可选写法)
sumIf(isSlow, isError = 0) AS slowCount
sumIf(值, 条件) :满足条件时才累加,比 CASE WHEN 更简洁。
为什么这个方案可行?
- 聚合函数内部定义了统计规则(只统计纯慢调用),不受 WHERE 影响
- list 接口的 WHERE 过滤逻辑不受干扰,三个状态(成功/错误/慢调用)严格分离
修复后测试通过,点"异常"和点"仅看慢调用",数字一致了。
七、复盘:我学到了什么
1. 排查方法论(可复用) F12 抓请求 → 对比参数差异 → URL → Controller → Service → Repository(一层层追) → 找到 WHERE / 过滤条件 → 对比不同场景下过滤条件是否一致 → 不一致的地方就是 Bug
2. ClickHouse 条件聚合函数
| 函数 | 作用 | 等价标准SQL |
|---|---|---|
sumIf(值, 条件) |
满足条件时求和 | sum(CASE WHEN 条件 THEN 值 END) |
countIf(条件) |
满足条件时计数 | count(CASE WHEN 条件 THEN 1 END) |
avgIf(值, 条件) |
满足条件时求平均 | 类似 |
ClickHouse 专属优化,比标准SQL简洁。
3. 方案分析的意识
这个问题让我意识到:改一个地方之前,先想清楚会影响哪些地方。
我一开始想直接改 WHERE 条件,但会影响 list 接口;想置 null,两个列表会串数据。最后发现让聚合函数自己闭环才是正确思路
这是我在实习期间修复的第一个Bug,不大,但从排查到修复完整走了一遍。 如果你也在用 ClickHouse 做统计,记住:聚合函数里套条件,比 WHERE 过滤更可控