1. ClickHouse 的物化视图是什么?
ClickHouse 的物化视图(Materialized View)是一种特殊的表,它根据预定义的查询语句自动维护数据。与传统的关系数据库中的视图不同,物化视图在物理上存储了查询结果,而不是在每次查询时重新计算。这意味着物化视图可以显著提高查询性能,特别是对于复杂的聚合查询。
2. ClickHouse 的物化视图解决了什么问题?
- 查询性能提升:物化视图通过预先计算并存储查询结果,减少了每次查询时的计算开销,从而显著提高了查询性能。
- 减少资源消耗:对于频繁执行的复杂查询,物化视图可以减少 CPU 和内存的使用,降低系统负载。
- 实时数据分析:物化视图可以实时更新,确保查询结果的时效性,适用于需要实时数据分析的场景。
- 简化查询:用户可以直接查询物化视图,而无需编写复杂的查询语句,简化了数据访问和分析的过程。
3. ClickHouse 的物化视图的应用场景
- 实时报表:生成实时报表,如用户行为分析、销售统计等,物化视图可以预先计算并存储聚合结果,提高报表生成的速度。
- 日志分析:处理和分析大规模的日志数据,如 Web 服务器日志、应用程序日志等,物化视图可以预先计算常见的聚合指标。
- 用户行为分析:分析用户的行为数据,如点击流、购买记录等,物化视图可以预先计算用户的行为特征。
- 金融数据分析:处理高频交易数据,进行风险管理、市场分析等,物化视图可以预先计算复杂的金融指标。
- 物联网数据分析:处理 IoT 设备产生的大量数据,如传感器数据、设备状态等,物化视图可以预先计算设备的状态和性能指标。
4. ClickHouse 的物化视图的底层原理
-
定义和创建:
- 物化视图通过
CREATE MATERIALIZED VIEW
语句定义,并指定一个TO
子句,指向一个目标表。 - 物化视图的定义中包含一个
SELECT
查询,该查询的结果会被插入到目标表中。
- 物化视图通过
-
数据插入:
- 当数据插入到源表时,ClickHouse 会自动触发物化视图的
SELECT
查询,并将结果插入到目标表中。 - 这个过程是异步的,确保不会影响源表的插入性能。
- 当数据插入到源表时,ClickHouse 会自动触发物化视图的
-
数据更新:
- 物化视图的目标表是普通的 ClickHouse 表,可以进行常规的查询和操作。
- 如果需要更新物化视图的数据,可以通过删除和重建物化视图来实现。
创建物化视图
scss
CREATE MATERIALIZED VIEW [IF NOT EXISTS] [db.]Materialized_name [TO[db.]name] [ON CLUSTER cluster]
ENGINE = engine_name()
ORDER BY expr
[POPULATE]
AS SELECT ...
参数 | 说明 |
---|---|
db |
数据库的名称,默认为当前选择的数据库,本文以default为例。 |
Materialized_name |
物化视图名。 |
TO[db.]name |
将物化视图的数据写入到新表中。说明 如果需要将物化视图的数据写入新表,不能使用POPULATE 关键字。 |
[ON CLUSTER cluster] |
在每一个节点上都创建一个物化视图,固定为ON CLUSTER default 。 |
ENGINE = engine_name() |
表引擎类型,具体请参见表引擎。 |
[POPULATE] |
POPULATE 关键字。如果创建物化视图时指定了POPULATE 关键字,则在创建时将SELECT 子句所指定的源表数据插入到物化视图中。不指定POPULATE 关键字时,物化视图只会包含在物化视图创建后新写入源表的数据。说明 一般不推荐使用POPULATE 关键字,因为在物化视图创建期间写入源表的数据将不会写入物化视图中。 |
SELECT ... |
SELECT 子句。当数据写入物化视图中SELECT 子句所指定的源表时,插入的数据会通过SELECT 子句查询进行转换并将最终结果插入到物化视图中。说明 SELECT 查询可以包含DISTINCT 、GROUP BY 、ORDER BY 和LIMIT 等,但是相应的转换是在每个插入数据块上独立执行的。 |
创建物化视图的限制:
1.必须指定物化视图的engine 用于数据存储
2.TO [db].[table]语法的时候,不得使用POPULATE。
3.查询语句(select)可以包含下面的子句: DISTINCT, GROUP BY, ORDER BY, LIMIT...
4.雾化视图的alter操作有些限制,操作起来不大方便。
5.若物化视图的定义使用了TO [db.]name 子语句,则可以将目标表的视图 卸载 DETACH 在装载 ATTACH
物化视图的数据更新:
1.物化视图创建好之后,若源表被写入新数据则物化视图也会同步更新
2.POPULATE 关键字决定了物化视图的更新策略: 若有POPULATE 则在创建视图的过程会将源表已经存在的数据一并导入,类似于 create table ... as 若无POPULATE 则物化视图在创建之后没有数据,只会在创建只有同步之后写入源表的数据. clickhouse 官方并不推荐使用populated,因为在创建物化视图的过程中同时写入的数据不能被插入物化视图。
3.物化视图不支持同步删除,若源表的数据不存在(删除了)则物化视图的数据仍然保留
4.物化视图是野种特殊的数据表,可以用show tables 查看
- ClickHouse中的物化视图的实现更像是插入触发器。 如果视图查询中存在某种汇总,则仅适用于这批新插入的数据。
- 对源表的现有数据进行的任何更改(例如更新,删除,删除分区等)都不会更改物化视图。
js
CREATE TABLE order (
order_id UInt64,
user_id UInt64,
order_date Date,
amount Float64
) ENGINE = MergeTree()
ORDER BY (order_date, user_id);
drop view daily_sale_mv
-- 创建目标表(显式定义)
CREATE TABLE daily_sale_target (
order_date Date,
total_sales Float64
) ENGINE = SummingMergeTree()
ORDER BY (order_date);
-- 创建指向目标表的物化视图
CREATE MATERIALIZED VIEW daily_sale_mv
TO daily_sale_target
AS
SELECT
order_date,
SUM(amount) AS total_sales
FROM order
GROUP BY order_date;
INSERT INTO order (order_id, user_id, order_date, amount) VALUES
(1, 101, '2023-10-01', 100.0),
(2, 102, '2023-10-01', 200.0),
(3, 103, '2023-10-02', 150.0),
(4, 104, '2023-10-02', 250.0);
INSERT INTO order (order_id, user_id, order_date, amount) VALUES
(5, 101, '2023-10-01', 100.0),
(6, 102, '2023-10-01', 200.0),
(7, 103, '2023-10-02', 150.0),
(8, 104, '2023-10-02', 250.0);
select * from daily_sale_target
--两次 INSERT 操作的影响:
每次 INSERT 都会生成新的数据部分(part)
物化视图会为每个新部分单独计算聚合
这些部分尚未被合并,所以您看到的是两个部分的独立聚合结果
--- `SummingMergeTree` 不会立即合并数据,而是在后台异步合并
--手动触发合并
OPTIMIZE TABLE daily_sale_target;
ClickHouse 物化视图指定表与不指定表的区别
特性 指定目标表 (TO) 不指定目标表 目标表控制权 完全控制 有限控制 目标表引擎 可自由选择 必须声明 表结构修改 可独立修改目标表 需要重建物化视图 数据插入 可直接向目标表插入 只能通过源表触发 维护复杂度 需要维护两个对象 单一对象
SummingMergeTree 的 columns 参数使用说明
SummingMergeTree([columns])
中的可选 columns
参数用于:
- 明确指定哪些列需要被求和聚合
- 其他未指定的列会保留第一次插入的值(或任意值,因为合并时非聚合列的值是不确定的)
例子为什么没指定
js
CREATE TABLE daily_sale_target (
order_date Date,
total_sales Float64
) ENGINE = SummingMergeTree()
ORDER BY (order_date);
没有指定 `columns` 参数是因为:
1. 表结构非常简单,只有两个列
1. `order_date` 是排序键,自然成为聚合键
1. `total_sales` 是唯一的数值列,会自动被识别为需要求和的列
改进后:
js
CREATE TABLE daily_sale_target (
order_date Date,
total_sales Float64
) ENGINE = SummingMergeTree(['total_sales'])
ORDER BY (order_date);
应该指定 columns 参数的场景
当表中有多个数值列,但只想对其中部分列求和时,就需要明确指定:
js
CREATE TABLE example (
date Date,
user_id UInt32,
clicks UInt32,
views UInt32,
revenue Float64
) ENGINE = SummingMergeTree(['clicks', 'views', 'revenue'])
ORDER BY (date, user_id);