ClickHouse Projection 与物化索引查询加速的轻量级方案一、物化视图的维护负担Projection 的轻量级替代ClickHouse 的物化视图Materialized View是查询加速的常用手段但它有一个显著的工程负担每个物化视图都是独立的物理表新数据插入时需要同步更新。如果源表有 10 个物化视图每次 INSERT 都会触发 10 次额外的写入操作导致写入放大和延迟增加。ClickHouse 21.6 引入了 Projection投影特性它允许在同一个表中定义多个数据投影——每个投影是数据的一种物理组织方式如不同的排序键、聚合粒度但与源表共享同一份 INSERT 流无需额外的写入操作。Projection 是介于物化视图和索引之间的轻量级加速方案。二、Projection 的底层机制Projection 的核心思想是一份数据多种组织。同一个 MergeTree 表可以定义多个 Projection每个 Projection 有自己的 ORDER BY 和聚合逻辑。查询时优化器根据查询条件选择最优的 Projection 执行。flowchart TD A[INSERT 数据] -- B[主表存储: ORDER BY (date, id)] A -- C[Projection 1: ORDER BY (user_id, date)] A -- D[Projection 2: 聚合: SUM(amount) GROUP BY date] B C D -- E[同一份 INSERT 流, 无写入放大] F[SELECT 查询] -- G[优化器选择最优 Projection] G -- H{查询模式} H --|按 user_id 查询| C H --|按 date 聚合| D H --|其他查询| B三、Projection 的代码实践3.1 创建 Projection 加速查询-- 原始表订单表 CREATE TABLE orders ( order_id UInt64, user_id UInt32, product_id UInt32, amount Decimal(12, 2), status LowCardinality(String), create_date Date, create_time DateTime ) ENGINE MergeTree() PARTITION BY toYYYYMM(create_date) ORDER BY (create_date, order_id); -- 问题查询 1按 user_id 查询订单 -- 主表 ORDER BY 是 (create_date, order_id)按 user_id 查询需要全分区扫描 SELECT * FROM orders WHERE user_id 12345 ORDER BY create_time DESC LIMIT 20; -- 扫描行数全分区 -- 创建 Projection按 user_id 重新排序 ALTER TABLE orders ADD PROJECTION proj_by_user ( SELECT * ORDER BY user_id, create_time DESC ); -- 物化 Projection对已有数据生效 ALTER TABLE orders MATERIALIZE PROJECTION proj_by_user; -- 再次查询优化器自动选择 proj_by_user SELECT * FROM orders WHERE user_id 12345 ORDER BY create_time DESC LIMIT 20; -- 扫描行数仅匹配 user_id 的行 -- 问题查询 2按日期聚合金额 SELECT create_date, SUM(amount), COUNT() FROM orders GROUP BY create_date; -- 需要扫描所有行的 amount 字段 -- 创建聚合 Projection预聚合日期维度 ALTER TABLE orders ADD PROJECTION proj_daily_agg ( SELECT create_date, SUM(amount), COUNT() GROUP BY create_date ); ALTER TABLE orders MATERIALIZE PROJECTION proj_daily_agg; -- 再次查询直接读取预聚合结果 SELECT create_date, SUM(amount), COUNT() FROM orders GROUP BY create_date; -- 扫描行数仅 365 行每天一行3.2 Projection 与物化视图的对比-- 物化视图方案传统 -- 需要创建独立的表INSERT 时额外写入 CREATE MATERIALIZED VIEW orders_daily_mv ENGINE SummingMergeTree() PARTITION BY toYYYYMM(create_date) ORDER BY (create_date) AS SELECT create_date, SUM(amount) as total_amount, COUNT() as order_count FROM orders GROUP BY create_date; -- Projection 方案推荐 -- 与主表共享 INSERT 流无额外写入 ALTER TABLE orders ADD PROJECTION proj_daily_agg ( SELECT create_date, SUM(amount), COUNT() GROUP BY create_date ); -- 写入放大对比 -- 物化视图每次 INSERT 触发 1 次主表写入 1 次 MV 写入 2x -- Projection每次 INSERT 触发 1 次主表写入Projection 随主表一起更新 1x3.3 Projection 管理-- 查看 Projection 状态 SELECT name, type, data_paths FROM system.projections WHERE database production AND table orders; -- 暂停 Projection不删除但优化器不再使用 -- 注意ClickHouse 目前不支持直接 disable需要删除重建 ALTER TABLE orders DROP PROJECTION proj_by_user; -- 重建 Projection数据变更后需要重新物化 ALTER TABLE orders MATERIALIZE PROJECTION proj_daily_agg; -- 强制查询使用特定 Projection调试用 SET force_primary_key 1; SELECT * FROM orders WHERE user_id 12345 SETTINGS optimize_use_projections 1;四、Projection 的边界分析与架构权衡存储开销。每个 Projection 是数据的额外物理副本。一个按user_id排序的 Projection 需要存储完整的行数据存储开销约等于主表。聚合 Projection 的开销较小仅存储聚合结果但失去了查询原始行的能力。写入性能影响。虽然 Projection 不像物化视图那样产生独立的 INSERT但每次 INSERT 需要同时更新主表和所有 ProjectionCPU 开销随 Projection 数量线性增长。建议单个表的 Projection 不超过 3 个。Projection 的一致性。Projection 的物化是异步的新插入的数据可能尚未出现在 Projection 中。查询时ClickHouse 会自动合并主表和 Projection 的结果保证一致性但合并过程有额外开销。适用边界Projection 最适合查询模式固定、写入量中等的场景。对于高吞吐写入场景如日志采集多个 Projection 可能成为写入瓶颈。对于查询模式多变的场景Projection 的覆盖范围有限应考虑物化视图或外部聚合表。五、总结ClickHouse Projection 是介于物化视图和索引之间的轻量级查询加速方案。它通过一份数据多种组织的方式在不增加写入放大的前提下提供多种查询优化路径。落地时需关注存储开销、写入性能影响和一致性保障。建议从聚合 Projection 开始存储开销小逐步引入排序 Projection单个表的 Projection 总数控制在 3 个以内。
ClickHouse Projection 与物化索引:查询加速的轻量级方案
发布时间:2026/6/11 19:11:13
ClickHouse Projection 与物化索引查询加速的轻量级方案一、物化视图的维护负担Projection 的轻量级替代ClickHouse 的物化视图Materialized View是查询加速的常用手段但它有一个显著的工程负担每个物化视图都是独立的物理表新数据插入时需要同步更新。如果源表有 10 个物化视图每次 INSERT 都会触发 10 次额外的写入操作导致写入放大和延迟增加。ClickHouse 21.6 引入了 Projection投影特性它允许在同一个表中定义多个数据投影——每个投影是数据的一种物理组织方式如不同的排序键、聚合粒度但与源表共享同一份 INSERT 流无需额外的写入操作。Projection 是介于物化视图和索引之间的轻量级加速方案。二、Projection 的底层机制Projection 的核心思想是一份数据多种组织。同一个 MergeTree 表可以定义多个 Projection每个 Projection 有自己的 ORDER BY 和聚合逻辑。查询时优化器根据查询条件选择最优的 Projection 执行。flowchart TD A[INSERT 数据] -- B[主表存储: ORDER BY (date, id)] A -- C[Projection 1: ORDER BY (user_id, date)] A -- D[Projection 2: 聚合: SUM(amount) GROUP BY date] B C D -- E[同一份 INSERT 流, 无写入放大] F[SELECT 查询] -- G[优化器选择最优 Projection] G -- H{查询模式} H --|按 user_id 查询| C H --|按 date 聚合| D H --|其他查询| B三、Projection 的代码实践3.1 创建 Projection 加速查询-- 原始表订单表 CREATE TABLE orders ( order_id UInt64, user_id UInt32, product_id UInt32, amount Decimal(12, 2), status LowCardinality(String), create_date Date, create_time DateTime ) ENGINE MergeTree() PARTITION BY toYYYYMM(create_date) ORDER BY (create_date, order_id); -- 问题查询 1按 user_id 查询订单 -- 主表 ORDER BY 是 (create_date, order_id)按 user_id 查询需要全分区扫描 SELECT * FROM orders WHERE user_id 12345 ORDER BY create_time DESC LIMIT 20; -- 扫描行数全分区 -- 创建 Projection按 user_id 重新排序 ALTER TABLE orders ADD PROJECTION proj_by_user ( SELECT * ORDER BY user_id, create_time DESC ); -- 物化 Projection对已有数据生效 ALTER TABLE orders MATERIALIZE PROJECTION proj_by_user; -- 再次查询优化器自动选择 proj_by_user SELECT * FROM orders WHERE user_id 12345 ORDER BY create_time DESC LIMIT 20; -- 扫描行数仅匹配 user_id 的行 -- 问题查询 2按日期聚合金额 SELECT create_date, SUM(amount), COUNT() FROM orders GROUP BY create_date; -- 需要扫描所有行的 amount 字段 -- 创建聚合 Projection预聚合日期维度 ALTER TABLE orders ADD PROJECTION proj_daily_agg ( SELECT create_date, SUM(amount), COUNT() GROUP BY create_date ); ALTER TABLE orders MATERIALIZE PROJECTION proj_daily_agg; -- 再次查询直接读取预聚合结果 SELECT create_date, SUM(amount), COUNT() FROM orders GROUP BY create_date; -- 扫描行数仅 365 行每天一行3.2 Projection 与物化视图的对比-- 物化视图方案传统 -- 需要创建独立的表INSERT 时额外写入 CREATE MATERIALIZED VIEW orders_daily_mv ENGINE SummingMergeTree() PARTITION BY toYYYYMM(create_date) ORDER BY (create_date) AS SELECT create_date, SUM(amount) as total_amount, COUNT() as order_count FROM orders GROUP BY create_date; -- Projection 方案推荐 -- 与主表共享 INSERT 流无额外写入 ALTER TABLE orders ADD PROJECTION proj_daily_agg ( SELECT create_date, SUM(amount), COUNT() GROUP BY create_date ); -- 写入放大对比 -- 物化视图每次 INSERT 触发 1 次主表写入 1 次 MV 写入 2x -- Projection每次 INSERT 触发 1 次主表写入Projection 随主表一起更新 1x3.3 Projection 管理-- 查看 Projection 状态 SELECT name, type, data_paths FROM system.projections WHERE database production AND table orders; -- 暂停 Projection不删除但优化器不再使用 -- 注意ClickHouse 目前不支持直接 disable需要删除重建 ALTER TABLE orders DROP PROJECTION proj_by_user; -- 重建 Projection数据变更后需要重新物化 ALTER TABLE orders MATERIALIZE PROJECTION proj_daily_agg; -- 强制查询使用特定 Projection调试用 SET force_primary_key 1; SELECT * FROM orders WHERE user_id 12345 SETTINGS optimize_use_projections 1;四、Projection 的边界分析与架构权衡存储开销。每个 Projection 是数据的额外物理副本。一个按user_id排序的 Projection 需要存储完整的行数据存储开销约等于主表。聚合 Projection 的开销较小仅存储聚合结果但失去了查询原始行的能力。写入性能影响。虽然 Projection 不像物化视图那样产生独立的 INSERT但每次 INSERT 需要同时更新主表和所有 ProjectionCPU 开销随 Projection 数量线性增长。建议单个表的 Projection 不超过 3 个。Projection 的一致性。Projection 的物化是异步的新插入的数据可能尚未出现在 Projection 中。查询时ClickHouse 会自动合并主表和 Projection 的结果保证一致性但合并过程有额外开销。适用边界Projection 最适合查询模式固定、写入量中等的场景。对于高吞吐写入场景如日志采集多个 Projection 可能成为写入瓶颈。对于查询模式多变的场景Projection 的覆盖范围有限应考虑物化视图或外部聚合表。五、总结ClickHouse Projection 是介于物化视图和索引之间的轻量级查询加速方案。它通过一份数据多种组织的方式在不增加写入放大的前提下提供多种查询优化路径。落地时需关注存储开销、写入性能影响和一致性保障。建议从聚合 Projection 开始存储开销小逐步引入排序 Projection单个表的 Projection 总数控制在 3 个以内。