ClickHouseNotes

第 09 章:SummingMergeTree

zjc 于 2026-01-09 发布

这是《ClickHouse 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 SummingMergeTree 会在后台合并时把排序键相同的数值列求和,用于降低预聚合指标的物理行数。查询时仍需要 sum(),否则结果可能不完整。

9.1 基本语义

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

写入:

INSERT INTO analytics.city_metric_local VALUES
('2026-08-25', 1, 'page_view', 100, 80, 0),
('2026-08-25', 1, 'page_view', 50, 40, 0),
('2026-08-25', 1, 'order', 10, 9, 1000.00);

合并后前两行可能变成:

metric_date=2026-08-25, city_id=1, metric_type=page_view, pv=150, uv=120

注意:uv 直接相加变成 120,可能重复计数。不是所有指标都能简单求和。

9.2 可累加指标

适合:

指标 说明
PV 可累加
请求次数 可累加
GMV 可累加
时长总量 可累加
错误数 可累加

不适合:

指标 原因
UV 用户可能重复
余额 应取最新而非求和
转化率 应由分子分母计算
平均单价 应由总额 / 数量计算
去重设备数 需近似或明细集合

UV 的处理方式:

  1. 保留明细表;
  2. 使用 uniqState 物化聚合状态;
  3. 使用近似 HLL 类型;
  4. 接受业务定义的累加误差。

9.3 聚合状态方案

普通 MergeTree 保存聚合状态:

CREATE TABLE analytics.city_metric_state
(
    metric_date Date,
    city_id UInt32,
    metric_type LowCardinality(String),
    pv AggregateFunction(count),
    uv_state AggregateFunction(uniq, UInt64),
    gmv SimpleAggregateFunction(sum, Decimal64(2))
)
ENGINE = AggregatingMergeTree
PARTITION BY toYYYYMM(metric_date)
ORDER BY (metric_date, city_id, metric_type);

写入:

INSERT INTO analytics.city_metric_state
SELECT
    metric_date,
    city_id,
    metric_type,
    countState() AS pv,
    uniqState(user_id) AS uv_state,
    sumState(gmv) AS gmv
FROM analytics.events_local
GROUP BY metric_date, city_id, metric_type;

查询:

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

当 SummingMergeTree 无法表达指标语义时,AggregatingMergeTree 更合适。

9.4 查询仍需聚合

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

原因:

未合并前:多行
部分合并后:仍可能有多个较大 part
查询时:必须再 sum 才能得到最终结果

把 SummingMergeTree 当成“减少行数的存储优化”,而不是查询层免聚合的保证。

9.5 非数值列处理

排序键相同的行合并时,非数值列保留某一行。若查询依赖非数值列,必须把它放进排序键或用聚合函数表达。

示例:

CREATE TABLE analytics.channel_metric
(
    metric_date Date,
    city_id UInt32,
    channel LowCardinality(String),
    pv UInt64
)
ENGINE = SummingMergeTree
ORDER BY (metric_date, city_id, channel);

如果 channel 不同,它们本来就是不同分组,不会被合并成一行。

9.6 建模流程

1. 明确业务指标定义
2. 区分可累加与不可累加
3. 确定维度组合
4. 设置 ORDER BY 为维度组合
5. 选择 Summing 或 Aggregating
6. 查询时统一聚合
7. 与明细表对账

维度组合通常是:

日期 + 城市 + 渠道 + 指标类型

如果不同报表使用完全不同维度,建议按主题建多个汇总表,而不是一张超宽表覆盖所有场景。

9.7 物化视图配合

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

这里 uv 的误差来自同一用户跨批次重复。若需要准确 UV,使用聚合状态或保留明细。

9.8 与 ReplacingMergeTree 对比

维度 ReplacingMergeTree SummingMergeTree
合并语义 保留一行 数值列求和
业务问题 去重 可累加汇总
版本 可指定 无相同概念
典型数据 订单状态 指标预聚合
查询 argMax / FINAL sum

不要把状态数据和可累加指标混在同一个引擎里解决。语义不清时优先普通 MergeTree,再在物化视图中处理。

9.9 排查与对账

检查维度组合:

SELECT metric_date, city_id, metric_type, count()
FROM analytics.city_metric_local
GROUP BY metric_date, city_id, metric_type
HAVING count() > 1
LIMIT 100;

与明细对账:

SELECT
    count() AS detail_pv,
    sum(amount) AS detail_gmv
FROM analytics.events_local
WHERE event_date = '2026-08-25' AND event_type = 'pay';

汇总表:

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

如果结果不一致,检查物化视图启动时间、过滤条件、维度空值、金额精度和事件丢失。

本章小结

SummingMergeTree 适合写入端已经聚合出的可累加指标。它通过合并减少行数,但查询仍要聚合。UV、比率、平均值等指标不能盲目相加,必要时使用 AggregatingMergeTree 或保留明细。

思考题

  1. 为什么查询 SummingMergeTree 仍要 sum()
  2. 哪些指标不能直接累加?
  3. AggregatingMergeTree 解决什么问题?
  4. 物化视图如何造成 UV 误差?
  5. 如何设计汇总表与明细表的对账?