1 概念
物化视图是将预先计算(根据定义好的 SELECT 语句)好的数据集,存储在 Doris 中的一个特殊的表。
物化视图的出现主要是为了满足用户,既能对原始明细数据的任意维度分析,也能快速的对固定维度进行分析查询。
首先,什么是物化视图?
从定义上来说,就是包含了查询结果的数据库对象,可能是对远程数据的本地Copy;也可能是一个表或多表Join后结果的行或列的子集;也可能是聚合后的结果。说白了,就是预先存储查询结果的一种数据库对象。
2 适用场景
- 分析需求覆盖明细数据查询以及固定维度查询两方面。
- 查询仅涉及表中的很小一部分列或行。
- 查询包含一些耗时处理操作,比如:时间很久的聚合操作等。
- 查询需要匹配不同前缀索引。
3 优势
- 对于那些经常重复的使用相同的子查询结果的查询性能大幅提升。
- Doris自动维护物化视图的数据,无论是新的导入,还是删除操作都能保证base 表和物化视图表的数据一致性。无需任何额外的人工维护成本。
- 查询时,会自动匹配到最优物化视图,并直接从物化视图中读取数据。
自动维护物化视图的数据会造成一些维护开销,会在后面的物化视图的局限性中展开说明。
4 物化视图 VS Rollup
在没有物化视图功能之前,用户一般都是使用 Rollup 功能通过预聚合方式提升查询效率的。但是 Rollup 具有一定的局限性,他不能基于明细模型做预聚合。
物化视图则在覆盖了 Rollup 的功能的同时,还能支持更丰富的聚合函数。所以物化视图其实是 Rollup 的一个超集。
也就是说,之前 ALTER TABLE ADD ROLLUP 语法支持的功能现在均可以通过 CREATE MATERIALIZED VIEW 实现。
5 使用物化视图
Doris 系统提供了一整套对物化视图的 DDL 语法,包括创建,查看,删除。DDL 的语法和 PostgreSQL, Oracle都是一致的。
5.1 创建物化视图
这里首先你要根据你的查询语句的特点来决定创建一个什么样的物化视图。这里并不是说你的物化视图定义和你的某个查询语句一模一样就最好。这里有两个原则:
- 从查询语句中抽象出,多个查询共有的分组和聚合方式作为物化视图的定义。
- 不需要给所有维度组合都创建物化视图。
首先第一个点,一个物化视图如果抽象出来,并且多个查询都可以匹配到这张物化视图。这种物化视图效果最好。因为物化视图的维护本身也需要消耗资源。
如果物化视图只和某个特殊的查询很贴合,而其他查询均用不到这个物化视图。则会导致这张物化视图的性价比不高,既占用了集群的存储资源,还不能为更多的查询服务。
所以用户需要结合自己的查询语句,以及数据维度信息去抽象出一些物化视图的定义。
第二点就是,在实际的分析查询中,并不会覆盖到所有的维度分析。所以给常用的维度组合创建物化视图即可,从而到达一个空间和时间上的平衡。
通过下面命令就可以创建物化视图了。创建物化视图是一个异步的操作,也就是说用户成功提交创建任务后,Doris 会在后台对存量的数据进行计算,直到创建成功。
CREATE MATERIALIZED VIEW
具体的语法可以通过下面命令查看:
HELP CREATE MATERIALIZED VIEW
5.2 支持聚合函数
目前物化视图创建语句支持的聚合函数有:
- SUM, MIN, MAX (Version 0.12)
- COUNT, BITMAP_UNION, HLL_UNION (Version 0.13)
- BITMAP_UNION 的形式必须为:BITMAP_UNION(TO_BITMAP(COLUMN)) column 列的类型只能是整数(largeint也不支持), 或者 BITMAP_UNION(COLUMN) 且 base 表为 AGG 模型。
- HLL_UNION 的形式必须为:HLL_UNION(HLL_HASH(COLUMN)) column 列的类型不能是 DECIMAL , 或者 HLL_UNION(COLUMN) 且 base 表为 AGG 模型
使用限制:
- DML:delete
使用物化视图功能后,由于物化视图实际上是损失了部分维度数据的。所以对表的 DML类型操作会有一些限制。
例如:如果表的物化视图Key中不包含删除语句中的条件列,则删除语句不能执行。如果想要删除渠道为APP端的数据,由于存在一个物化视图并不包含渠道这个字段,则这个删除不能执行,因为删除在物化视图中无法被执行。这时候你只能把物化视图先删除,然后删除完数据后,重新构建一个新的物化视图。 未来还会支持构建年表、月表,会用的到 to_mouth 和 to_day 函数。
5.3 更新策略
为保证物化视图表和 Base 表的数据一致性, Doris 会将导入,删除等对 base 表的操作都同步到物化视图表中。并且通过增量更新的方式来提升更新效率。通过事务方式来保证原子性。
比如如果用户通过 INSERT 命令插入数据到 base 表中,则这条数据会同步插入到物化视图中。当 base 表和物化视图表均写入成功后,INSERT 命令才会成功返回。
5.4 查询自动匹配
物化视图创建成功后,用户的查询不需要发生任何改变,也就是还是查询的 base 表。Doris 会根据当前查询的语句去自动选择一个最优的物化视图,从物化视图中读取数据并计算。
用户可以通过 EXPLAIN 命令来检查当前查询是否使用了物化视图。
物化视图中的聚合和查询中聚合的匹配关系:
5.5 查询物化视图
查看当前表都有哪些物化视图,以及他们的表结构都是什么样的。通过下面命令:
desc table all;
6 演示
6.1 创建base表
CREATE TABLE sales_records(
id INT COMMENT "销售记录的id",
seller_id INT COMMENT "销售员的id",
store_id INT COMMENT "门店的id",
sale_date DATE COMMENT "售卖时间",
sale_amt BIGINT COMMENT "金额"
)
DISTRIBUTED BY HASH(id) BUCKETS 10;
6.2 创建物化视图
CREATE MATERIALIZED VIEW mv_1 AS
SELECT seller_id,sale_date,SUM(sale_amt)
FROM sales_records
GROUP BY seller_id,sale_date;
提交完创建物化视图的任务后,Doris就会异步在后台生成物化视图的数据,构建物化视图。在构建期间,用户依然可以正常的查询和导入新的数据。创建任务会自动处理当前的存量数据和所有新到达的增量数据,从而保持和Base表的数据一致性。用户无需担心一致性问题。
6.3 查询物化视图
desc sales_records all;
6.4 物化视图匹配
基于这个Base表的数据再次提交两个创建物化视图的任务
CREATE MATERIALIZED VIEW mv_2 AS SELECT store_id,sale_date,SUM(sale_amt)
FROM sales_records
GROUP BY store_id,sale_date;
CREATE MATERIALIZED VIEW mv_3 AS
SELECT seller_id,SUM(sale_amt)
FROM sales_records
GROUP BY seller_id;
查询物化视图
desc sales_records all;
执行查询语句,Doris会更加视图情况进行自动匹配相关视图
select seller_id,SUM(sale_amt) from sales_records where sale_date='2021-02-10' group by seller_id;
6.5 自动匹配
最优选择如下图:
物化视图的自动匹配分为下面两个步骤:
- 根据查询条件筛选出一个最优的物化视图:这一步的输入是所有候选物化视图表的元数据,根据查询的条件从候选集中输出最优的一个物化视图
- 根据选出的物化视图对查询进行改写:这一步是结合上一步选择出的最优物化视图,进行查询的改写,最终达到直接查询物化视图的目的。
删除、检查物化视图是否构建完成
-- 删除物化视图
DROP MATERIALIZED VIEW mv_3 ON sales_records;
-- 检查物化视图是否构建完成
SHOW ALTER TABLE MATERIALIZED VIEW FROM test_db;
6.6 选择最优
下面详细解释一下第一步最优物化视图是被如何选择出来的
这里分为两个步骤:
- 对候选集合进行一个过滤。只要是查询的结果能从物化视图数据计算(取部分行,部分列,或部分行列的聚合)出都可以留在候选集中,过滤完成后候选集合大小 >= 1。
- 从候选集合中根据聚合程度,索引等条件选出一个最优的也就是查询花费最少物化视图。
这里再举一个相对复杂的例子,来体现这个过程。
首先先说过滤候选集这个过程。
候选集过滤目前分为4层,每一层过滤后去除不满足条件的物化视图。
例如:查询2月10日各个销售员都买了多少钱。
首先候选集中包括所有的物化视图以及Base表共4个。
- 第一层过滤先判断查询Where中的谓词涉及到的数据是否能从物化视图中得到,也就是销售时间列是否在表中存在。由于第三个物化视图中根本不存在销售时间列。所以在这一层过滤中,mv_3就被淘汰了。
- 第二层是过滤查询的分组列是否为候选集的分组列的子集,也就是销售员id是否为表中分组列的子集。由于第二个物化视图中的分组列并不涉及销售员id。所以在这一层过滤中,mv_2也被淘汰了。
- 第三层过滤是看查询的聚合列是否为候选集中聚合列的子集,也就是对销售额求和是否能从候选集的表中聚合得出。这里Base表和物化视图表均满足标准。
- 最后一层是过滤看查询需要的列是否存在于候选集合的列中。由于候选集合中的表均满足标准,所以最终候选集合中的表为销售明细表,以及mv_1这两张。
候选集过滤完后输出一个集合,这个集合中的所有表都能满足查询的需求,但每张表的查询效率都不同。
这时候就需要在这个集合根据前缀索引是否能匹配到,以及聚合程度的高低来选出一个最优的物化视图。
例如:从表结构中可以看出,Base表的销售日期列是一个非排序列,而物化视图表的日期是一个排序列,同时聚合程度上mv_1表明显比Base表高。
所以最后选择出mv_1作为该查询的最优匹配。
6.7 查询改写
最后再根据选择出的最优解,改写查询。
例如:刚才的查询选中mv_1后,将查询改写为从mv_1中读取数据,过滤出日志为2月10日的mv_1中的数据然后返回即可。
有些情况下的查询改写还会涉及到查询中的聚合函数的改写。比如业务方经常会用到Count、Distinct对PV、UV进行计算。
例如:
广告点击明细记录表中存放哪个用户点击了什么广告,从什么渠道点击的,以及点击的时间。并且在这个Base表基础上构建了一个物化视图表,存储了不同广告不同渠道的用户Bitmap值。
由于bitmap_union这种聚合方式本身会对相同的用户user_id进行一个去重聚合。当用户查询广告在Web端的UV的时候,就可以匹配到这个物化视图。匹配到这个物化视图表后就需要对查询进行改写,将之前的对用户id求 count(distinct) 改为对物化视图中bitmap_union列求count。
所以最后查询取物化视图的第一和第三行求Bitmap聚合中有几个值。
6.8 适用场景
上面介绍了两种数据模型——明细模型和聚合模型,也介绍了两种预聚合方式物化视图和Rollup。那么在数据模型的选择上:
- 如果用户的分析都是固定维度的分析类查询,比如报表类业务,且完全不关心明细数据时,则用聚合模型最合适。
- 如果用户需要查询明细数据,比如交易明细,则用明细模型合适。
对于物化视图和Rollup来说:
- 他们的共同点都是通过预聚合的方式来提升查询效率。
实际上物化视图是Rollup的一个超集,在覆盖Rollup的工作同时,还支持更灵活的聚合方式。
因此,如果对数据的分析需求既覆盖了明细查询也存在分析类查询,则可以先创建一个明细模型的表,并构建物化视图。
7 局限性
- 物化视图的聚合函数的参数不支持表达式仅支持单列,比如: sum(a+b)不支持。
- 如果删除语句的条件列,在物化视图中不存在,则不能进行删除操作。如果一定要删除数据,则需要先将物化视图删除,然后方可删除数据。
- 单表上过多的物化视图会影响导入的效率:导入数据时,物化视图和 base 表数据是同步更新的,如果一张表的物化视图表超过10张,则有可能导致导入速度很慢。
- 相同列,不同聚合函数,不能同时出现在一张物化视图中,比如:select sum(a), min(a) from table 不支持。
- 物化视图针对 Unique Key数据模型,只能改变列顺序,不能起到聚合的作用,所以在Unique Key模型上不能通过创建物化视图的方式对数据进行粗粒度聚合操作