这是《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 的处理方式:
- 保留明细表;
- 使用
uniqState物化聚合状态; - 使用近似 HLL 类型;
- 接受业务定义的累加误差。
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 或保留明细。
思考题
- 为什么查询 SummingMergeTree 仍要
sum()? - 哪些指标不能直接累加?
- AggregatingMergeTree 解决什么问题?
- 物化视图如何造成 UV 误差?
- 如何设计汇总表与明细表的对账?