You need to enable JavaScript to run this app.
导航
同步物化视图
最近更新时间:2024.12.04 14:29:11首次发布时间:2024.02.05 16:08:27

物化视图功能定义

ByteHouse 数据库中的视图(View),除了 普通视图(Normal View) 以外,还有一种 物化视图(Materialized View)。

  • 普通视图:普通视图没有真正存储数据,不占用存储空间。只是读取数据的执行操作,可以看作是一条保存过的 SQL 查询语句。
  • 物化视图:物化视图 存储了 SQL 查询语句包含的数据,并提供更新机制。用查询物化视图来替代直接查询数据表,可以避免对数据进行再次的计算与聚合,能够以空间换时间的方式节省查询时间,达到查询加速和简化查询逻辑的目的。

物化视图的工作原理基于查询重写机制和数据更新。查询重写机制对应用透明,数据库引擎优化器会自动选择合适的物化视图进行查询重写,无需修改原有查询语句。同时,物化视图提供自动刷新机制,确保数据的及时性和准确性。
在大数据环境下,物化视图特别适用于数据仓库和查询引擎中频繁执行的复杂查询场景。它通过以空间换时间的方式,提高查询效率,简化查询逻辑,并显著降低大型表上复杂查询的资源消耗。作为数据查询的加速器,物化视图在保持数据实时性的同时,极大地增强了数据分析效率。它优化了数据仓库中的大规模复杂查询处理,提升了整体的数据分析体验。
总的来说,物化视图通过预计算和存储查询结果,结合自动的查询重写和数据更新机制,在不改变原有查询语句的情况下,有效提升了数据库查询性能,为大数据环境下的复杂查询场景提供了强有力的支持。

基本概念

  • 基表(Base Table)- 物化视图的驱动表, 异步物化视图支持join,subquery多表的关联查询。
  • 目的表(Target Table)- 物化视图实际存储的表,一般是AggregateMergeTree 或者MergeTree
  • 物化视图 (Materialized View) - 逻辑概念定义基表到目的表的映射关系
  • 刷新 (Refresh) - 创建物化视图后,其中的数据仅反映创建时刻基表的状态。当基表中的数据发生变化时,需要通过刷新通过视图从基表执行查询映射到目的表。
  • 查询改写(Query Rewrite)- 查询改写是指在对已构建了物化视图的基表进行查询时,系统自动判断是否可以复用物化视图中的预计算结果处理查询。如果可以复用,系统会直接从相关的物化视图读取预计算结果,以避免重复计算消耗系统资源和时间。

物化视图类型

视图类型

单表聚合

多表关联

查询改写

刷新策略

数据导入影响

异步物化视图

异步刷新
手动刷新

同步物化视图

导入同步刷新
手动刷新

同步视图(Synchronous Materialized View)

特点:

  • 数据实时性高:源表数据变更时,物化视图立即更新。
  • 强一致性:保证源表和物化视图数据的一致性。
  • 写入延迟:可能会增加源表的写入延迟。

优势:

  • 数据始终保持最新状态,适用于对实时性要求极高的场景。
  • 查询时可以保证获取到最新数据。
  • 适合数据量相对较小,更新频率不是特别高的场景。

异步视图(Asynchronous Materialized View)

特点:

  • 异步更新:源表数据变更和物化视图更新是分离的。
  • 低延迟:对源表的写入几乎没有影响。
  • 最终一致性:物化视图数据可能会有短暂的滞后。

优势:

  • 对源表写入性能影响小,适合高并发写入场景。
  • 支持更复杂的视图查询形态,查询改写匹配支持场景更多。
  • 可以更好地控制资源使用,避免峰值时期的性能问题。
  • 适合大规模数据处理,特别是在数据仓库和分析系统中。
  • 可以通过配置参数灵活调整刷新策略,平衡实时性和系统负载。

总的来说,同步视图和异步视图各有其优势和适用场景。选择哪种类型的物化视图,需要根据具体的业务需求、数据特征和系统资源情况来权衡。在实际应用中,可能会同时使用这两种类型的视图来满足不同的需求。

同步视图

同步物化视图的本质就是类似一种触发器,当基表有数据写入,会触发视图执行定义的 SQL,写入另外一张表,更新粒度是导入基表的数据块,基表与视图目的表同时写入成功事务才能结束,由此可以看出同步视图会影响基表的导入性能,而且只能支持单表的场景,对于基表通过merge进行数据合并的场景也不能支持,因为基表变化不能传导视图目的表,不能支持类似unique table作为基表。
目前在 ByteHouse 根据物化视图的用途分为如下使用场景:

  • Aggregate聚合物化视图,提升特定聚合查询的性能
  • Normal修改主键排序物化视图,提升对含有非主键列过滤条件查询性能
  • Realtime实时消费物化视图,用于对实时数据进行加工,产出数据

相关语法

--创建同步视图
CREATE MATERIALIZED VIEW [IF NOT EXISTS] [database.]<mv_name> [TO [database.]<target_name>]  AS <query_statement> [SETTINGS <mv_query_settings>];

--手动刷新视图
REFRESH MATERIALIZED VIEW db_name.mv_name [PARTITION partition_name] [WHERE predicate_expr] [ASYNC | SYNC];

Image

支持场景

  • 基表类型支持CnchMergeTree表,普通视图,实时消费表
  • 单表聚合,支持各种聚合算子,支持配置过滤条件
  • 单表无聚合,修改排序键,缩减读取字段,支持配置过滤条件

限制场景

  • 不支持多视图级联更新
  • 基表不支持Unique表
  • 由于存在数据不一致的风险,默认不支持视图查询改写
  • 由于存在数据不一致的风险,默认不支持多表查询视图

如果存在如上要求,推荐使用异步物化视图。
下面以一个行为分析系统的事件表来说明上述视图的使用方法。

源表定义
--创建数据库
create database mv;

--数据源表
CREATE TABLE mv.events(
  app_id UInt32,
  server_time UInt64,
  event_name String,
  uid UInt64,
  cost UInt64,
  duration UInt64,
  event_date Date
) ENGINE = CnchMergeTree PARTITION BY toDate(event_date)
ORDER BY
  (app_id, uid, event_name);

Aggregate聚合视图

Image
Aggregate聚合视图是物化视图最为常用的一种场景,基于特定的聚合查询对源数据抽取存为物化视图,由于聚合查询已经聚合为中间数据状态,查询视图会减少聚合计算,提高查询性能,后续的查询能命中视图,引擎对原始查询进行改写,直接查询聚合视图表。下面看具体的场景

视图定义

--视图目标表
CREATE TABLE mv.events_aggregation (
  app_id UInt32,
  event_name String,
  event_date Date,
  sum_cost AggregateFunction(sum, UInt64),
  max_duration AggregateFunction(max, UInt64)
) ENGINE = CnchAggregatingMergeTree() PARTITION BY toDate(event_date)
ORDER BY
  (app_id, event_name, event_date);

--视图定义
CREATE MATERIALIZED VIEW mv.events_aggregate_view to mv.events_aggregation (app_id UInt32,  event_name String, event_date Date, sum_cost AggregateFunction(sum, UInt64), max_duration AggregateFunction(max, UInt64)) AS SELECT
     app_id,
     event_name,
     event_date,
     sumState(cost) AS sum_cost,
     maxState(duration) AS max_duration
FROM mv.events
GROUP BY app_id, event_name, event_date; 

建表实践

  • 源表一般引擎定义为CnchMergeTree,暂时不支持带UNIQUE KEY的表 (由于unique key会自动合并相同unique key的行,但是物化视图并不能感知这个变化,会造成源表和视图的数据不一致)
  • 目标表引擎为CnchAggregatingMergeTree, 此引擎类型会在Merge阶段,对聚合SQL的group by相同字段进行合并,减少数据量,例子中对app_id, event_name, event_date相同行进行聚合计算
  • 目标表中对带State后缀的聚合函数得到的结果的数据类型是 AggregateFunction 类型,sumState(cost) 对应 AggregateFunction(sum, UInt64),UIn64为cost的类型
  • 视图定义中建议使用to 指明目标表,这样比较明确容易理解,聚合函数需要在后面添加后缀State,例如sumState(cost), maxState(duration), 之所以如此,是因为在物化视图的单一数据文件中保存的聚合值只是部分数据的聚合结果(Partial Aggregate Result),是个中间状态的数据,实际查询时需要把不同数据节点上不同数据分片的相同分组的中间态结果 merge 到一起。
  • 视图定义中group by 字段的顺序决定目标的排序键顺序,需要根据业务需求,决定排序,把查询过滤条件中经常用到,并且维度基数较低的字段排在最前面,这样会提高查询性能,例子中,目标的order by字段与 group by字段相同。
  • 目标表和源表的分区partition定义必须一致,否则refresh命令将不能运行

导入数据

---明细表
insert into table mv.events(app_id, server_time, event_name, uid, cost, duration, event_date) values (1, 1642149961, 'show', 121245, 3454, 64, '2022-06-14');
insert into table mv.events(app_id, server_time, event_name, uid, cost, duration, event_date) values (2, 1642149961 , 'send', 2345, 476, 64, '2022-06-14');
insert into table mv.events(app_id, server_time, event_name, uid, cost, duration, event_date) values (3, 1642150683, 'show', 544545, 87, 5434, '2022-06-14');
insert into table mv.events(app_id, server_time, event_name, uid, cost, duration, event_date) values (3, 1642150683, 'show', 544545, 930, 232, '2022-06-14');
insert into table mv.events(app_id, server_time, event_name, uid, cost, duration, event_date) values (4, 1642150683, 'slide', 234545, 123, 98, '2022-06-14');
insert into table mv.events(app_id, server_time, event_name, uid, cost, duration, event_date) values (5, 1642150683, 'click', 131312, 2644, 26, '2022-06-14');

Normal物化视图

Image
在业务频繁迭代的场景,经常需要使用非主键过滤条件进行查询,但是主表的主键顺序又不能修改,基于这种需求,可以定义物化视图来修改主键顺序,根据业务需求裁切部分列或者根据某些条件过滤数据,来产出视图。

视图定义

--视图目标表
CREATE TABLE mv.events_normal (
  app_id UInt32,
  event_name String,
  event_date Date,
  uid UInt64,
  cost UInt64
) ENGINE = CnchMergeTree() PARTITION BY toDate(event_date)
ORDER BY (uid, event_name);

--视图定义
CREATE MATERIALIZED VIEW mv.events_normal_view to mv.events_normal (app_id UInt32,
  event_name String,
  event_date Date,
  uid UInt64,
  cost UInt64) AS SELECT
     app_id,
     event_name,
     event_date,
     uid,
     cost
FROM mv.events;

建表实践

  • 目标表与源表引擎类型相同一般都为CnchMergeTree,分区键保持一致,主键顺序根据业务需求定义
  • 视图定义直接根据目标表字段类型和要求进行选取,无需定义order by字段

实时物化视图

Image
实时消费以Kafka的消费为例,视图的SQL定义需要从consumer获取数据,可以全部获取作为明细表,可以进行聚合,过滤,投影等操作,可以是并联视图,或者串联视图。

视图定义

--实时消费consumer表定义
CREATE TABLE mv.events_consumer (
  app_id UInt32,
  server_time UInt64,
  event_name String,
  uid UInt64,
  cost UInt64,
  duration UInt64,
  event_date Date
) ENGINE = CnchKafka() SETTINGS kafka_broker_list = 'XXXX:9092',
kafka_topic_list = 'ch_qa_cnch_staging_yg',
kafka_group_name = 'events_consumer_group',
kafka_format = 'JSONEachRow',
kafka_row_delimiter = '\n',
kafka_num_consumers = 5,
kafka_max_block_size = 65536;

---明细表视图定义
CREATE MATERIALIZED VIEW mv.events_real_all_view to mv.events (
  app_id UInt32,
  server_time UInt64,
  event_name String,
  uid UInt64,
  cost UInt64,
  duration UInt64,
  event_date Date
) AS
SELECT * FROM mv.events_consumer;

---聚合表视图定义
CREATE MATERIALIZED VIEW mv.events_real_aggregate_view to mv.events_aggregation (app_id UInt32,  event_name String, event_date Date, sum_cost AggregateFunction(sum, UInt64), max_duration AggregateFunction(max, UInt64)) AS SELECT
     app_id,
     event_name,
     event_date,
     sumState(cost) AS sum_cost,
     maxState(duration) AS max_duration
FROM mv.events_consumer
GROUP BY app_id, event_name, event_date; 

--normal表实时消费表
CREATE MATERIALIZED VIEW mv.events_real_normal_view to mv.events_normal (app_id UInt32,  event_name String, event_date Date, sum_cost AggregateFunction(sum, UInt64), max_duration AggregateFunction(max, UInt64)) AS  SELECT
     app_id,
     event_name,
     event_date,
     uid,
     cost
FROM mv.events_consumer where uid = 5434;

建表实践

  • 视图定义中的源表是实时消费表
  • 目前定义新的视图,需要重启实时消费,否则会造成数据不更新
  • 上述定义三个视图会产生三份数据,定义很多视图或者复杂的聚合视图,会影响实时消费的性能