实战案例1:基于Web日志的PV/UV实时聚合
从原始表到物化视图的完整链路,咱们今天就走一遍。
说实话,很多刚接触ClickHouse的朋友,一上来就问我:“老师,PV/UV统计到底怎么用物化视图?” 我通常的回答是——你先别急着写SQL,先搞清楚数据长什么样,再想怎么聚合。
好,咱们直接开干。
1. 原始数据长什么样?
假设我们有一个Web日志表,记录每次用户访问。字段大概是这样:
| 字段名 | 类型 | 说明 |
|---|---|---|
| event_time | DateTime | 访问时间 |
| user_id | String | 用户ID |
| page_url | String | 访问页面 |
| ip | String | 客户端IP |
| status | UInt16 | HTTP状态码 |
嗯,这里要注意:user_id 如果是未登录用户,可能是空字符串或者匿名标识。我在项目中遇到过,有些日志系统直接把未登录用户记为 null,结果物化视图里 uniq 函数算出来的UV直接翻倍——因为 null 被当成独立值了。所以,建议统一处理成 '' 或者 'anonymous'。
2. 创建原始表
咱们用 MergeTree 引擎,按时间分区。我个人习惯按天分区,查询效率高,而且物化视图的增量更新也方便。
CREATE TABLE web_logs (
event_time DateTime,
user_id String,
page_url String,
ip String,
status UInt16
) ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(event_time)
ORDER BY (event_time, user_id);
你想想看,为什么排序键要加 user_id?因为后面物化视图里要按用户去重,排序键里带上它,聚合时能减少数据扫描量。
3. 创建物化视图:按分钟聚合PV/UV
核心逻辑来了。我们要实时统计每分钟的PV(页面访问次数)和UV(独立用户数)。
ClickHouse 的物化视图,说白了就是一个“触发器”——数据写入原始表时,自动触发聚合计算,结果存到另一张表里。
CREATE MATERIALIZED VIEW mv_web_pv_uv_minute
ENGINE = SummingMergeTree()
PARTITION BY toYYYYMMDD(minute)
ORDER BY (minute, page_url)
AS SELECT
toStartOfMinute(event_time) AS minute,
page_url,
count() AS pv,
uniq(user_id) AS uv
FROM web_logs
GROUP BY minute, page_url;
这里我用了 SummingMergeTree,它会在后台自动合并相同排序键的行,把 pv 和 uv 累加起来。不过要注意:uniq 函数在物化视图里是近似去重,误差一般在1%以内。如果你的业务要求精确去重,可以用 uniqExact,但性能会下降不少。
uniqExact 配合 AggregatingMergeTree。但大部分Web分析场景,uniq 足够了。
4. 查询物化视图
数据写入原始表后,物化视图会自动更新。查询时直接查视图表,速度飞快。
SELECT
minute,
page_url,
pv,
uv
FROM mv_web_pv_uv_minute
WHERE minute >= now() - INTERVAL 1 HOUR
ORDER BY minute DESC;
我曾经帮一个客户优化过类似的查询。他们原来直接查原始表,一小时的数据要跑3秒多。换成物化视图后,同样的查询只要20毫秒。你想想看,差了150倍!
5. 完整链路图
下面这张图,展示了从原始表到物化视图的完整数据流。我特意用SVG画的,方便你理解。
AS SELECT 中的查询,把结果写入目标表。查询时直接查目标表,速度极快。
6. 避坑指南
- 数据延迟: 物化视图是异步的,数据写入后不会立即出现在视图里。我遇到过有人刚写入就查视图,结果查不到数据。其实等几秒就好了,或者用
OPTIMIZE TABLE手动触发合并。 - 重复数据: 如果原始表有重复写入,物化视图里的
count()会累加。建议在原始表写入时做好去重,或者在物化视图里用uniq处理。 - 分区对齐: 物化视图的分区键最好和原始表一致。我曾经图省事,用了不同的分区策略,结果数据合并时出了不少问题。
INSERT 或 ALTER 操作!它的数据完全由物化视图自动管理。手动修改会导致数据不一致,甚至视图崩溃。
7. 性能对比
咱们用数据说话。假设原始表有1亿行数据,按小时查询PV/UV:
| 查询方式 | 扫描数据量 | 响应时间 |
|---|---|---|
| 直接查原始表 | 1亿行 | 3~5秒 |
| 查物化视图 | 几千行 | 10~50毫秒 |
说白了,物化视图就是用空间换时间。多占一点存储,换来几百倍的查询加速。对于实时看板、监控报表这类场景,这笔买卖太划算了。
好,这个案例就到这里。你可以在自己的ClickHouse环境里跑一遍,感受一下从原始表到物化视图的完整链路。有什么问题,咱们下一章接着聊。