ClickHouseNotes

第 13 章:物化视图

zjc 于 2026-01-13 发布

这是《ClickHouse 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 物化视图把查询结果落到目标表中,常用于明细数据实时预聚合。它是一条随写入触发的数据处理管道,不是简单的查询别名。

13.1 普通视图与物化视图

普通视图只保存 SQL:

CREATE VIEW analytics.events_view
AS
SELECT * FROM analytics.events_local
WHERE is_deleted = 0;

物化视图保存结果:

CREATE MATERIALIZED VIEW analytics.city_metric_mv
TO analytics.city_metric_local
AS
SELECT
    event_date AS metric_date,
    city_id,
    count() AS pv,
    sum(amount) AS gmv
FROM analytics.events_local
GROUP BY event_date, city_id;

普通视图查询时执行,物化视图在源表写入时更新目标表。

13.2 TO 表结构

推荐显式指定 TO 目标表:

CREATE TABLE analytics.city_metric_local
(
    metric_date Date,
    city_id UInt32,
    pv UInt64,
    gmv Decimal64(2)
)
ENGINE = SummingMergeTree
PARTITION BY toYYYYMM(metric_date)
ORDER BY (metric_date, city_id);

再创建视图:

CREATE MATERIALIZED VIEW analytics.city_metric_mv
TO analytics.city_metric_local
AS
SELECT ...

优点:

  1. 目标表 schema 清晰;
  2. 方便重建视图;
  3. 可以选择目标引擎;
  4. 支持 TTL 和分区;
  5. 便于备份恢复。

13.3 触发语义

物化视图由新写入的数据块触发:

INSERT block
   |
   +-> 写入源表
   |
   +-> 对该 block 执行视图 SELECT
           |
           v
       写入目标表

这意味着:

  1. 视图创建前的历史数据不会自动回填;
  2. 每个 insert block 内的聚合范围有限;
  3. 目标表查询通常仍要聚合;
  4. 视图异常会影响写入链路;
  5. 删除源表数据不会自动删除目标数据。

13.4 回填历史

先暂停写入,再手动回填:

INSERT INTO analytics.city_metric_local
SELECT
    event_date AS metric_date,
    city_id,
    count() AS pv,
    sum(amount) AS gmv
FROM analytics.events_local
WHERE event_date < '2026-08-25'
GROUP BY event_date, city_id;

再创建物化视图处理新数据。关键是避免同一时间窗重复回填。

13.5 聚合状态

准确 UV 可以使用聚合状态:

CREATE TABLE analytics.city_metric_state
(
    metric_date Date,
    city_id UInt32,
    uv_state AggregateFunction(uniq, UInt64),
    pv SimpleAggregateFunction(sum, UInt64)
)
ENGINE = AggregatingMergeTree
ORDER BY (metric_date, city_id);

物化视图:

CREATE MATERIALIZED VIEW analytics.city_metric_state_mv
TO analytics.city_metric_state
AS
SELECT
    event_date AS metric_date,
    city_id,
    uniqState(user_id) AS uv_state,
    count() AS pv
FROM analytics.events_local
GROUP BY event_date, city_id;

查询:

SELECT city_id, uniqMerge(uv_state) AS uv, sum(pv) AS pv
FROM analytics.city_metric_state
GROUP BY city_id;

13.6 常见用途

用途 示例
预聚合 明细转日汇总
数据清洗 过滤脏数据、补默认值
维度补齐 Lookup 后写入宽表
多目标分发 一份明细写多个汇总
指标状态 uniqState、quantileState
格式转换 JSON 转结构化列

13.7 视图链路

events_local
   |
   +-> mv_daily -> daily_metrics
   |
   +-> mv_city -> city_metrics
   |
   +-> mv_realtime -> realtime_metrics

多条视图会同时消耗写入线程资源。要控制视图数量、目标表写入频率、聚合复杂度、失败重试策略和延迟监控。

13.8 管理操作

查看:

SELECT database, name, engine, as_select
FROM system.tables
WHERE is_materialized_view = 1;

删除:

DROP VIEW analytics.city_metric_mv;

物化视图定义修改通常通过删除重建完成。操作前要确认写入暂停窗口和目标表数据边界。

13.9 一致性

物化视图不提供跨表事务保证,可能出现源表可见而目标表暂不可见的情况。

排查角度:

  1. 写入时间;
  2. 视图创建时间;
  3. 分区边界;
  4. 历史回填重复;
  5. 目标表合并状态;
  6. 视图异常日志。

对账源表:

SELECT count(), sum(amount)
FROM analytics.events_local
WHERE event_date = '2026-08-25';

对账目标表:

SELECT sum(pv), sum(gmv)
FROM analytics.city_metric_local
WHERE metric_date = '2026-08-25';

13.10 反模式

反模式 问题
用视图替代所有模型设计 链路难维护
视图中做复杂 JOIN 写入放大
目标表无 TTL 数据无限增长
忽略 block 内聚合语义 UV 等指标不准
频繁删除重建 数据边界混乱
不监控视图异常 静默丢数据

本章小结

物化视图是写入触发的实时加工管道,适合预聚合、清洗和多目标分发。推荐使用 TO 表并显式管理回填边界。聚合状态能处理去重类指标,但不能把物化视图当作事务一致的实时副本。

思考题

  1. 物化视图何时触发?
  2. 为什么历史数据需要手动回填?
  3. uniqState 和 uniqMerge 如何配合?
  4. 物化视图有哪些一致性限制?
  5. 如何对账源表和汇总表?