物化视图是将预先计算(根据定义好的 SELECT 语句)好的数据集,存储在 Doris 中的一个特殊的表。
物化视图的出现主要是为了满足用户,既能对原始明细数据的任意维度分析,也能快速的对固定维度进行分析查询。
适用场景:
优势:
物化视图 VS Rollup:
在没有物化视图功能之前,用户一般都是使用 Rollup 功能通过预聚合方式提升查询效率的。但是 Rollup 具有一定的局限性,他不能基于明细模型做预聚合。
物化视图则在覆盖了 Rollup 的功能的同时,还能支持更丰富的聚合函数。所以物化视图其实是 Rollup 的一个超集。
也就是说,之前 ALTER TABLE ADD ROLLUP 语法支持的功能现在均可以通过 CREATE MATERIALIZED VIEW 实现。
局限性:
物化视图的聚合函数的参数不支持表达式仅支持单列,比如: sum(a+b)不支持。
如果删除语句的条件列,在物化视图中不存在,则不能进行删除操作。如果一定要删除数据,则需要先将物化视图删除,然后方可删除数据。
单表上过多的物化视图会影响导入的效率:导入数据时,物化视图和 base 表数据是同步更新的,如果一张表的物化视图表超过10张,则有可能导致导入速度很慢。这就像单次导入需要同时导入10张表数据是一样的。
相同列,不同聚合函数,不能同时出现在一张物化视图中,比如:select sum(a), min(a) from table 不支持。
物化视图针对 Unique Key数据模型,只能改变列顺序,不能起到聚合的作用,所以在Unique Key模型上不能通过创建物化视图的方式对数据进行粗粒度聚合操作
Doris 系统提供了一整套对物化视图的 DDL 语法,包括创建,查看,删除。DDL 的语法和 PostgreSQL, Oracle都是一致的。
这里首先你要根据你的查询语句的特点来决定创建一个什么样的物化视图。这里并不是说你的物化视图定义和你的某个查询语句一模一样就最好。这里有两个原则:
从查询语句中抽象出,多个查询共有的分组和聚合方式作为物化视图的定义。
不需要给所有维度组合都创建物化视图。
首先第一个点,一个物化视图如果抽象出来,并且多个查询都可以匹配到这张物化视图。这种物化视图效果最好。因为物化视图的维护本身也需要消耗资源。
如果物化视图只和某个特殊的查询很贴合,而其他查询均用不到这个物化视图。则会导致这张物化视图的性价比不高,既占用了集群的存储资源,还不能为更多的查询服务。
所以用户需要结合自己的查询语句,以及数据维度信息去抽象出一些物化视图的定义。
第二点就是,在实际的分析查询中,并不会覆盖到所有的维度分析。所以给常用的维度组合创建物化视图即可,从而到达一个空间和时间上的平衡。
创建物化视图是一个异步的操作,也就是说用户成功提交创建任务后,Doris 会在后台对存量的数据进行计算,直到创建成功。
具体的语法可以通过 Mysql 协议链接 Doris 并输入下面命令查看:
HELP CREATE MATERIALIZED VIEW
目前物化视图创建语句支持的聚合函数有:
为保证物化视图表和 Base 表的数据一致性, Doris 会将导入,删除等对 base 表的操作都同步到物化视图表中。并且通过增量更新的方式来提升更新效率。通过事务方式来保证原子性。
比如如果用户通过 INSERT 命令插入数据到 base 表中,则这条数据会同步插入到物化视图中。当 base 表和物化视图表均写入成功后,INSERT 命令才会成功返回。
物化视图创建成功后,用户的查询不需要发生任何改变,也就是还是查询的 base 表。Doris 会根据当前查询的语句去自动选择一个最优的物化视图,从物化视图中读取数据并计算。
用户可以通过 EXPLAIN 命令来检查当前查询是否使用了物化视图。
其中 bitmap 和 hll 的聚合函数在查询匹配到物化视图后,查询的聚合算子会根据物化视图的表结构进行一个改写。
查看当前表都有哪些物化视图,以及他们的表结构都是什么样的。通过下面命令:
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,以及他们的表结构。
如果用户不再需要物化视图,则可以通过命令删除物化视图。
具体的语法可以通过 Mysql 协议链接 Doris 输入下面命令查看:
HELP DROP MATERIALIZED VIEW
测试数据:
mysql> show create table ods_fact_sale\G
*************************** 1. row ***************************
Table: ods_fact_sale
Create Table: CREATE TABLE `ods_fact_sale` (
`id` bigint(20) NULL COMMENT "",
`sale_date` varchar(100) NULL COMMENT "",
`prod_name` varchar(32) NULL COMMENT "",
`sale_nums` bigint(20) SUM NULL DEFAULT "0" COMMENT "",
INDEX b_odsfactsale_prodname (`prod_name`) USING BITMAP COMMENT '产品名称'
) ENGINE=OLAP
AGGREGATE KEY(`id`, `sale_date`, `prod_name`)
COMMENT "OLAP"
DISTRIBUTED BY HASH(`id`) BUCKETS 10
PROPERTIES (
"replication_allocation" = "tag.location.default: 1",
"in_memory" = "false",
"storage_format" = "V2"
)
1 row in set (0.00 sec)
mysql> select count(*) from ods_fact_sale;
+-----------+
| count(*) |
+-----------+
| 767830000 |
+-----------+
1 row in set (16.94 sec)
此时开启一个查询:
select sale_date,sum(sale_nums) as all_nums
from ods_fact_sale
group by sale_date;
创建物化视图:
CREATE MATERIALIZED VIEW mv_odsfactsale_1 as
select sale_date,sum(sale_nums) as all_nums
from ods_fact_sale
group by sale_date;
测试记录:
mysql> CREATE MATERIALIZED VIEW mv_odsfactsale_1 as
-> select sale_date,sum(sale_nums) as all_nums
-> from ods_fact_sale
-> group by sale_date;
Query OK, 0 rows affected (0.02 sec)
mysql>
mysql>
mysql>
mysql>
mysql> desc ods_fact_sale all;
+------------------+---------------+-----------+--------------+------+-------+---------+-------+---------+
| IndexName | IndexKeysType | Field | Type | Null | Key | Default | Extra | Visible |
+------------------+---------------+-----------+--------------+------+-------+---------+-------+---------+
| ods_fact_sale | AGG_KEYS | id | BIGINT | Yes | true | NULL | | true |
| | | sale_date | VARCHAR(100) | Yes | true | NULL | | true |
| | | prod_name | VARCHAR(32) | Yes | true | NULL | | true |
| | | sale_nums | BIGINT | Yes | false | 0 | SUM | true |
| | | | | | | | | |
| mv_odsfactsale_1 | AGG_KEYS | sale_date | VARCHAR(100) | Yes | true | NULL | | true |
| | | sale_nums | BIGINT | Yes | false | 0 | SUM | true |
+------------------+---------------+-----------+--------------+------+-------+---------+-------+---------+
7 rows in set (0.00 sec)