Apache Doris 代码仓库地址:apache/incubator-doris 欢迎大家关注加星
物化视图是将预先计算(根据定义好的 SELECT 语句)好的数据集,存储在 doris中的一个特殊的表。
物化视图的出现主要是为了满足用户,既能对原始明细数据的任意维度分析,也能快速的对固定维度进行分析查询。
目前doris物化视图只支持单表,不支持多表join操作
自动维护物化视图的数据会造成一些维护开销,会在后面的物化视图的局限性中展开说明。
在没有物化视图功能之前,用户一般都是使用 Rollup 功能通过预聚合方式提升查询效率的。但是 Rollup 具有一定的局限性,他不能基于明细模型做预聚合。
物化视图则在覆盖了 Rollup 的功能的同时,还能支持更丰富的聚合函数。所以物化视图其实是 Rollup 的一个超集。
也就是说,之前 ALTER TABLE ADD ROLLUP 语法支持的功能现在均可以通过 CREATE MATERIALIZED VIEW 实现。
Palo 系统提供了一整套对物化视图的 DDL 语法,包括创建,查看,删除。DDL 的语法和 PostgreSQL, Oracle 都是一致的。
这里首先你要根据你的查询语句的特点来决定创建一个什么样的物化视图。这里并不是说你的物化视图定义和你的某个查询语句一模一样就最好。这里有两个原则:
首先第一个点,一个物化视图如果抽象出来,并且多个查询都可以匹配到这张物化视图。这种物化视图效果最好。因为物化视图的维护本身也需要消耗资源。
如果物化视图只和某个特殊的查询很贴合,而其他查询均用不到这个物化视图。则会导致这张物化视图的性价比不高,既占用了集群的存储资源,还不能为更多的查询服务。
所以用户需要结合自己的查询语句,以及数据维度信息去抽象出一些物化视图的定义。
第二点就是,在实际的分析查询中,并不会覆盖到所有的维度分析。所以给常用的维度组合创建物化视图即可,从而到达一个空间和时间上的平衡。
通过下面命令就可以创建物化视图了。创建物化视图是一个异步的操作,也就是说用户成功提交创建任务后,Palo 会在后台对存量的数据进行计算,直到创建成功。
具体的语法可以通过下面命令查看 CREATE MATERIALIZED VIEW。
目前物化视图创建语句支持的聚合函数有:
BITMAP_UNION(TO_BITMAP(COLUMN))
column 列的类型只能是整数(largeint也不支持), 或者 BITMAP_UNION(COLUMN)
且 base 表为 AGG 模型。HLL_UNION(HLL_HASH(COLUMN))
column 列的类型不能是 DECIMAL , 或者 HLL_UNION(COLUMN)
且 base 表为 AGG 模型。为保证物化视图表和 base 表的数据一致性, Palo 会将导入,删除等对 base 表的操作都同步到物化视图表中。并且通过增量更新的方式来提升更新效率。通过事务方式来保证原子性。
比如如果用户通过 INSERT 命令插入数据到 base 表中,则这条数据会同步插入到物化视图中。当 base 表和物化视图表均写入成功后,INSERT 命令才会成功返回。
物化视图创建成功后,用户的查询不需要发生任何改变,也就是还是查询的 base 表。Palo 会根据当前查询的语句去自动选择一个最优的物化视图,从物化视图中读取数据并计算。
用户可以通过 EXPLAIN 命令来检查当前查询是否使用了物化视图。
物化视图中的聚合和查询中聚合的匹配关系:
物化视图聚合 | 查询中聚合 |
---|
其中 bitmap 和 hll 的聚合函数在查询匹配到物化视图后,查询的聚合算子会根据物化视图的表结构进行一个改写。详细见实例2。
查看当前表都有哪些物化视图,以及他们的表结构都是什么样的。通过下面命令:
MySQL [test]> desc mv_test all;
+-----------+---------------+-----------------+----------+------+-------+---------+--------------+
| IndexName | IndexKeysType | Field | Type | Null | Key | Default | Extra |
+-----------+---------------+-----------------+----------+------+-------+---------+--------------+
| mv_test | DUP_KEYS | k1 | INT | Yes | true | NULL | |
| | | k2 | BIGINT | Yes | true | NULL | |
| | | k3 | LARGEINT | Yes | true | NULL | |
| | | k4 | SMALLINT | Yes | false | NULL | NONE |
| | | | | | | | |
| mv_2 | AGG_KEYS | k2 | BIGINT | Yes | true | NULL | |
| | | k4 | SMALLINT | Yes | false | NULL | MIN |
| | | k1 | INT | Yes | false | NULL | MAX |
| | | | | | | | |
| mv_3 | AGG_KEYS | k1 | INT | Yes | true | NULL | |
| | | to_bitmap(`k2`) | BITMAP | No | false | | BITMAP_UNION |
| | | | | | | | |
| mv_1 | AGG_KEYS | k4 | SMALLINT | Yes | true | NULL | |
| | | k1 | BIGINT | Yes | false | NULL | SUM |
| | | k3 | LARGEINT | Yes | false | NULL | SUM |
| | | k2 | BIGINT | Yes | false | NULL | MIN |
+-----------+---------------+-----------------+----------+------+-------+---------+--------------+
可以看到当前 mv_test
表一共有三张物化视图:mv_1, mv_2 和 mv_3,以及他们的表结构。
如果用户不再需要物化视图,则可以通过下面命令删除物化视图:DROP MATERIALIZED VIEW。
使用物化视图一般分为一下几个步骤:
首先是第一步:创建物化视图
假设用户有一张销售记录明细表,存储了每个交易的交易id,销售员,售卖门店,销售时间,以及金额。建表语句为:
create table sales_records(record_id int, seller_id int, store_id int, sale_date date, sale_amt bigint) distributed by hash(record_id) properties("replication_num" = "1");
这张 sales_records
的表结构如下:
MySQL [test]> desc sales_records;
+-----------+--------+------+-------+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------+------+-------+---------+-------+
| record_id | INT | Yes | true | NULL | |
| seller_id | INT | Yes | true | NULL | |
| store_id | INT | Yes | true | NULL | |
| sale_date | DATE | Yes | false | NULL | NONE |
| sale_amt | BIGINT | Yes | false | NULL | NONE |
+-----------+--------+------+-------+---------+-------+
这时候如果用户经常对不同门店的销售量进行一个分析查询,则可以给这个 sales_records
表创建一张以售卖门店分组,对相同售卖门店的销售额求和的一个物化视图。创建语句如下:
create materialized view store_amt as select store_id, sum(sale_amt) from sales_records group by store_id;
返回成功,则说明创建物化视图任务提交成功。
第二步:检查物化视图是否构建完成
由于创建物化视图是一个异步的操作,用户在提交完创建物化视图任务后,需要异步的通过命令检查物化视图是否构建完成。命令如下:
SHOW ALTER TABLE MATERIALIZED VIEW FROM db_name;
该命令的更多帮助请参阅:SHOW ALTER TABLE MATERIALIZED VIEW。
第三步:查询
当创建完成物化视图后,用户再查询不同门店的销售量时,就会直接从刚才创建的物化视图 store_amt
中读取聚合好的数据。达到提升查询效率的效果。
用户的查询依旧指定查询 sales_records
表,比如:
SELECT store_id, sum(sale_amt) FROM sales_records GROUP BY store_id;
上面查询就能自动匹配到 store_amt
。用户可以通过下面命令,检验当前查询是否匹配到了合适的物化视图。
MySQL [test]> EXPLAIN SELECT store_id, sum(sale_amt) FROM sales_records GROUP BY store_id;
+-----------------------------------------------------------------------------+
| Explain String |
+-----------------------------------------------------------------------------+
| PLAN FRAGMENT 0 |
| OUTPUT EXPRS: `store_id` | sum(`sale_amt`) |
| PARTITION: UNPARTITIONED |
| |
| RESULT SINK |
| |
| 4:EXCHANGE |
| |
| PLAN FRAGMENT 1 |
| OUTPUT EXPRS: |
| PARTITION: HASH_PARTITIONED: `store_id` |
| |
| STREAM DATA SINK |
| EXCHANGE ID: 04 |
| UNPARTITIONED |
| |
| 3:AGGREGATE (merge finalize) |
| | output: sum( sum(`sale_amt`)) |
| | group by: `store_id` |
| | |
| 2:EXCHANGE |
| |
| PLAN FRAGMENT 2 |
| OUTPUT EXPRS: |
| PARTITION: RANDOM |
| |
| STREAM DATA SINK |
| EXCHANGE ID: 02 |
| HASH_PARTITIONED: `store_id` |
| |
| 1:AGGREGATE (update serialize) |
| | STREAMING |
| | output: sum(`sale_amt`) |
| | group by: `store_id` |
| | |
| 0:OlapScanNode |
| TABLE: sales_records |
| PREAGGREGATION: ON |
| partitions=1/1 |
| rollup: store_amt |
| tabletRatio=10/10 |
| tabletList=22038,22040,22042,22044,22046,22048,22050,22052,22054,22056 |
| cardinality=0 |
| avgRowSize=0.0 |
| numNodes=1 |
+-----------------------------------------------------------------------------+
45 rows in set (0.006 sec)
其中最重要的就是 OlapScanNode 中的 rollup 属性。可以看到当前查询的 rollup 显示的是 store_amt
。也就是说查询已经正确匹配到物化视图 store_amt
, 并直接从物化视图中读取数据了。
业务场景: 计算广告的 UV,PV
假设用户的原始广告点击数据存储在 Palo,那么针对广告 PV, UV 查询就可以通过创建 bitmap_union
的物化视图来提升查询速度。
通过下面语句首先创建一个存储广告点击数据明细的表,包含每条点击的点击事件,点击的是什么广告,通过什么渠道点击,以及点击的用户是谁。
MySQL [test]> create table advertiser_view_record(time date, advertiser varchar(10), channel varchar(10), user_id int) distributed by hash(time) properties("replication_num" = "1");
Query O
K, 0 rows affected (0.014 sec)
原始的广告点击数据表结构为:
MySQL [test]> desc advertiser_view_record;
+------------+-------------+------+-------+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-------+---------+-------+
| time | DATE | Yes | true | NULL | |
| advertiser | VARCHAR(10) | Yes | true | NULL | |
| channel | VARCHAR(10) | Yes | false | NULL | NONE |
| user_id | INT | Yes | false | NULL | NONE |
+------------+-------------+------+-------+---------+-------+
4 rows in set (0.001 sec)
bitmap_union
的物化视图从而达到一个预先精确去重的效果。count(distinct)
聚合的结果和 bitmap_union_count
聚合的结果是完全一致的。而bitmap_union_count
等于 bitmap_union
的结果求 count, 所以如果查询中涉及到 count(distinct)
则通过>创建带 bitmap_union
聚合的物化视图方可加快查询。user_id
进行精确去重的物化视图。to_bitmap
转换为 bitmap 类型然后才可以进行 bitmap_union
聚合。advertiser_uv
中查询数据。比如原始的查询语句如下:user_id
字段求 count(distinct)
被改写为求 bitmap_union_count(to_bitmap)
。也就是通过 bitmap 的方式来达到精确去重的效果。业务场景:匹配更丰富的前缀索引
用户的原始表有 (k1, k2, k3) 三列。其中 k1, k2 为前缀索引列。这时候如果用户查询条件中包含 where k1=1 and k2=2
就能通过索引加速查询。
但是有些情况下,用户的过滤条件无法匹配到前缀索引,比如 where k3=3
。则无法通过索引提升查询速度。
创建以 k3 作为第一列的物化视图就可以解决这个问题。