是触发器不是缓存
ClickHouse 的物化视图不是「查询时现算」的普通视图,它是插入触发的:源表每写入一个数据块,视图就按 SELECT 逻辑算一遍并把结果写入目标表。写 TO 目标表的形式(CREATE MATERIALIZED VIEW mv TO target AS SELECT ...)最常用,也最好维护。
基本信息
| 适用版本 | ClickHouse 22.x 及之后版本均为同一套语义 |
|---|---|
| 标准写法 | CREATE MATERIALIZED VIEW mv TO target AS SELECT … FROM source |
| 触发时机 | 只处理插入源表的新数据块 |
| 历史回灌 | POPULATE 或 INSERT INTO target SELECT … |
| 常见目标引擎 | SummingMergeTree / AggregatingMergeTree / ReplacingMergeTree |
典型使用场景
- 实时预聚合:把明细事件按分钟聚合成指标表,查询侧只扫小表
- 字段转换与清洗:源表写原始 JSON,视图解析出列并写入结构化的目标表
- 列裁剪与排序键重构:把宽表投影成窄表,按查询模式重新排序
- 拆分维度:同一份源数据按不同维度拆到多张目标表,各查各的
- 级联聚合:分钟表再触发小时表、天表,形成分层汇总
示例
示例
CREATE TABLE events_by_minute
(
minute DateTime,
event_type LowCardinality(String),
cnt UInt64
)
ENGINE = SummingMergeTree
ORDER BY (minute, event_type);
CREATE MATERIALIZED VIEW mv_events_minute TO events_by_minute AS
SELECT toStartOfMinute(event_time) AS minute,
event_type,
count() AS cnt
FROM events
GROUP BY minute, event_type;
-- 回灌历史:用 INSERT SELECT,别用 POPULATE(会漏掉回灌期间的新写入)
INSERT INTO events_by_minute
SELECT toStartOfMinute(event_time), event_type, count()
FROM events WHERE event_time < now() - INTERVAL 1 DAY
GROUP BY 1, 2;
-- 核对增量是否在进:目标表行数随时间增长即为正常
SELECT count() FROM events_by_minute;要点对照
| 要点 | 说明 | 注意点 |
|---|---|---|
| 触发时机 | 只在向源表插入数据块时执行 | 历史数据不会自动回灌 |
| TO 目标表 | 结果写进显式表,引擎与结构自主选择 | 推荐写法,便于独立优化与重建 |
| 不带 TO | 视图自身持有数据 | 结构改动受限,查询效率不如显式目标表 |
| POPULATE | 创建时把已有数据写入 | 回灌期间的新写入会丢失 |
| 跨块收敛 | 按块聚合,跨块结果靠目标表引擎合并 | 目标表用普通 MergeTree 会导致计数重复累加 |
视图定义里的聚合是按数据块计算的,块与块之间的结果需要目标表引擎收敛:用 SummingMergeTree 汇总、用 AggregatingMergeTree 存中间状态,才能得到正确的最终值。
物化视图适用于「写多读少、聚合维度固定」的场景;如果查询维度经常变化,直接查明细表或改用投影(projection)往往更省事。
修改视图定义需要 DROP 后重建,重建窗口内源表新写入的数据不会再进目标表;稳妥做法是新建一个视图名并切换,事后用 INSERT SELECT 补齐缺口。