ClickHouse踩坑:一个sum()引发的"数字不一致"悬案

实习期间遇到的第一个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 过滤更可控

相关推荐
橘色的喵1 小时前
ReclaimBatcher 批量回收:RT-Thread 单核与 Linux SMP 通用
后端
攻城有术2 小时前
专项攻克-springcloud及其组件
后端·spring·spring cloud
Kripath_Rion3 小时前
带你速通计算机经典论文(一):分布式系统篇
分布式·后端·架构
似璟如你4 小时前
Java 开发者的 Go 语法基础:从 0 开始快速上手 Go
java·开发语言·后端·golang·go·编程语言
Codelinghu6 小时前
AI 写代码越强,程序员越不能只懂代码
后端
卡卡敲码6 小时前
Skills 撞车了,Agent 怎么选
后端
羑悻6 小时前
周一早上三件事砸过来,我以为要延期,结果 AI 替我扛了一半!
后端