物化视图的本质
ClickHouse 的物化视图不是查询缓存,而是插入触发器:源表每写入一个数据块,MV 里定义的 SELECT 就对该数据块执行一次,结果写进目标表。因此它只覆盖新写入的数据,表里已有的历史数据不会被动过,需要另外执行一次 INSERT SELECT 回填。做聚合加速时的标准组合是目标表用 AggregatingMergeTree,写入用 countState、sumState 这类状态函数,查询时用对应的 Merge 函数读回结果。
基本信息
| 本质 | 插入触发器,源表写入时执行并写入目标表 |
|---|---|
| 历史数据 | 不自动覆盖,需要手动 INSERT SELECT 回填 |
| 推荐目标引擎 | AggregatingMergeTree,承载聚合中间状态 |
| 写入函数 | countState、sumState、uniqState 等 |
| 查询函数 | countMerge、sumMerge、uniqMerge 等 |
| 与普通视图区别 | 普通视图只是查询别名,不存储数据 |
| 源表要求 | 通常为 MergeTree 家族引擎才能触发写入 |
| 与投影对比 | 投影在同表内提供另一种排序,物化视图把结果写到另一张表 |
典型使用场景
- 明细表加日汇总表:源表持续写入,汇总指标自动累加,报表只读小表
- 大表预聚合:把维度组合固定的聚合结果物化,避免每次查询都扫明细
- 写入时做数据抽取:把需要分析的字段抽到另一张窄表,去掉大字段降低成本
- 接入消息队列:Kafka 引擎表作为源,物化视图把消息同步写入 MergeTree 与聚合表
- 多套排序需求:主表保持一种排序,另外用物化视图为不同查询形态各建一份
聚合型物化视图
聚合型物化视图
-- 目标表:用聚合引擎承载中间状态
CREATE TABLE dw.trade_store_agg (
dt Date,
store_id String,
order_cnt AggregateFunction(count, UInt64),
pay_amt AggregateFunction(sum, Decimal(18,2))
)
ENGINE = AggregatingMergeTree
ORDER BY (dt, store_id);
-- 物化视图:源表每次写入都会触发这段查询
CREATE MATERIALIZED VIEW dw.mv_trade_store TO dw.trade_store_agg AS
SELECT dt, store_id,
countState() AS order_cnt, -- 写入的是状态而非最终值
sumState(pay_amt) AS pay_amt
FROM dw.trade_order
GROUP BY dt, store_id;
-- 历史数据必须手动回填:MV 不会自动覆盖已有数据
INSERT INTO dw.trade_store_agg
SELECT dt, store_id, countState(), sumState(pay_amt)
FROM dw.trade_order GROUP BY dt, store_id;
-- 查询:用 Merge 函数读出聚合结果
SELECT dt, store_id, countMerge(order_cnt) AS cnt, sumMerge(pay_amt) AS amt
FROM dw.trade_store_agg GROUP BY dt, store_id;对象行为对照
| 对象 | 是否存数据 | 生效范围 |
|---|---|---|
| 普通视图 | 不存,只是查询别名 | 查询时展开语句 |
| 物化视图 | 结果写入目标表 | 仅源表新写入时触发 |
| 历史数据 | 需手动 INSERT SELECT | 不包含在自动触发内 |
| 聚合状态列 | 需 Merge 函数读取 | 直接查得到中间值 |
查询聚合结果列必须用 countMerge、sumMerge 这类函数。直接查状态列拿到的是二进制中间值,看起来像乱码,容易被误判成数据损坏。
物化视图上线后一定记得回填历史数据。只建视图不回填,目标表里只有上线之后的数据,看板历史曲线会在上线当天出现一个清晰的台阶。