滑动「基数」改变去重数据的规模(独立用户数),观察四种算法的内存占用与误差变化。误差为模拟值,与官方误差范围一致。
uniqExact 内存随基数线性暴涨;
uniqHLL12 永远只占 ~3 KB;
uniq(HLL++ 自适应)兼顾速度和精度,是日常 UV 的默认选择。
| 函数 | 算法 | 内存 | 误差 | 推荐场景 |
|---|---|---|---|---|
uniqExact | 全量 hash set | O(N) | 0 | 反作弊、对账 |
uniq | HLL++ 自适应 | ~几十 KB | ~0.5% | 日常 UV / DAU 默认 |
uniqCombined | 小集合 hash + 大集合 HLL | 自适应 | ~0.1% | 中等量、要求高 |
uniqHLL12 | 标准 HLL 2^12 桶 | 固定 ~3 KB | ~1.5% | 极致省内存 |
点击左边几张分片表的「输出 -State」,看每张表把聚合「中间状态」(HLL 桶的二进制)传给中间区,最后在右边由 uniqMerge 合并成最终 UV。
AggregateFunction(uniq, UInt64) —— 这是 HLL 桶数组的二进制序列
uniq 不能简单相加:
三个 100 万 UV 的并集 ≠ 300 万。-State 保留 HLL 桶让我们能正确合并;
-Merge 把多个状态按位合并并最终估算。
| 角色 | SQL 写法 |
|---|---|
| 底表 schema | uv AggregateFunction(uniq, UInt64) |
| 物化视图写入 | SELECT day, uniqState(uid) AS uv ... |
| 查询时合并 | SELECT day, uniqMerge(uv) FROM ... GROUP BY day |
定义漏斗:view → click → addcart → pay,窗口 30 分钟。点击「步进」,看一个用户的事件流如何被算法逐步匹配。
windowFunnel(window_seconds)(ts, cond1, cond2, ...)
会从前往后扫一遍,遇到 cond1 开新链,遇到 cond_{level+1} 且离 cond1 在窗口内就 +1,最终返回最深步数。
SELECT
windowFunnel(1800)(
ts,
event_type='view',
event_type='click',
event_type='addcart',
event_type='pay'
) AS level,
count() AS users
FROM events GROUP BY uid
左边是 5 条原始记录(每条带一个 tags 数组),右边是 ARRAY JOIN 后的样子。点击按钮切换 ARRAY JOIN 与 LEFT ARRAY JOIN,看对空数组行的差异。
ARRAY JOIN 把空数组所在的行**丢掉**;
LEFT ARRAY JOIN 保留它,展开列取类型默认值(如 String 是 '')。
SELECT id, tag FROM events ARRAY JOIN tags AS tag; -- 空数组的行被丢 SELECT id, tag FROM events LEFT ARRAY JOIN tags AS tag; -- 空数组的行保留,tag = ''