Doris从入门到上天系列第五篇:Doris中的物化视图
一物化视图概念物化视图就是包含了查询结果的数据库对象可能是对远程数据的本地 copy也可能是一个表或多表 join 后结果的行或列的子集也可能是聚合后的结果。说白了就是预先存储查询结果的一种数据库对象。在 Doris 中的物化视图就是查询结果预先存储起来的特殊的表。物化视图的出现主要是为了满足用户既能对原始明细数据的任意维度分析也能快速的对固定维度进行分析查询。1适用场景每一次Base表也就是基表变化都会重新更新物化视图。Click的也有物化视图。跟Mysql的视图的结果就是把结果物化下来了。1分析需求覆盖明细数据查询以及固定维度查询两方面。2查询仅涉及表中的很小一部分列或行。3查询包含一些耗时处理操作比如时间很久的聚合操作等。4查询需要匹配不同前缀索引。物化视图就是RollUp的升级版。2优势对于那些经常重复使用相同子查询结果的查询性能大幅提升。Doris自动维护物化视图的数据无论是新的导入还是删除操作都能保证 base 表和物化视图表的数据一致性。无需任何额外的人工维护成本。查询时会自动匹配到最优物化视图并直接从物化视图中读取数据。自动维护物化视图的数据会造成一些维护开销会在后面的物化视图的局限性中展开说明3物化视图 vs roll up在没有物化视图功能之前用户一般都是使用 Rollup 功能通过预聚合方式提升查询效率的。但是 Rollup 具有一定的局限性它不能基于明细模型做预聚合。物化视图则在覆盖了 Rollup 的功能的同时还能支持更丰富的聚合函数。所以物化视图其实是 Rollup 的一个超集。也就是说之前 ALTER TABLE ADD ROLLUP 语法支持的功能现在均可以通过 CREATE MATERIALIZED VIEW 实现。二物化视图原理Doris 系统提供了一整套对物化视图的 DDL 语法包括创建查看删除。DDL 的语法和 PostgreSQL、Oracle 都是一致的。但是 Doris 目前创建物化视图只能在单表操作不支持 join。1创建物化视图创建物化视图的原则首先要根据查询语句的特点来决定创建一个什么样的物化视图。并不是说物化视图定义和某个查询语句一模一样就最好。这里有两个原则1从查询语句中抽象出多个查询共有的分组和聚合方式作为物化视图的定义。2不需要给所有维度组合都创建物化视图。首先第一个点一个物化视图如果抽象出来并且多个查询都可以匹配到这张物化视图这种物化视图效果最好。因为物化视图的维护本身也需要消耗资源。如果物化视图只和某个特殊的查询很贴合而其他查询均用不到这个物化视图。则会导致这张物化视图的性价比不高既占用了集群的存储资源还不能为更多的查询服务。所以用户需要结合自己的查询语句以及数据维度信息去抽象出一些物化视图的定义。第二点就是在实际的分析查询中并不会覆盖到所有的维度分析。所以给常用的维度组合创建物化视图即可从而到达一个空间和时间上的平衡。提取公共部分找到维度公共部分。一句话让更多的查询能够命中这个物化视图。通过下面命令就可以创建物化视图了。创建物化视图是一个异步的操作也就是说用户成功提交创建任务后Doris 会在后台对存量的数据进行计算直到创建成功。创建第一张物化视图Base 表基表CREATE TABLE sales_records ( record_id INT, seller_id INT, sale_date DATE, sale_amt BIGINT ) DISTRIBUTED BY HASH (record_id) BUCKETS 10;物化视图表CREATE MATERIALIZED VIEW seller_amt AS SELECT seller_id, sale_date, sum(sale_amt) FROM sales_records GROUP BY seller_id, sale_date;比如我们有一张销售记录明细表存储了每个交易的时间销售员销售门店和金额。提交完创建物化视图的任务后Doris 就会异步在后台生成物化视图的数据构建物化视图。在构建期间用户依然可以正常的查询和导入新的数据。创建任务会自动处理当前的存量数据和所有新到达的增量数据从而保持和 base 表的数据一致性。用户不需关心一致性问题。2物化视图查询物化视图创建完成后用户的查询会根据规则自动匹配到最优的物化视图。 比如我们有一张销售记录明细表并且在这个明细表上创建了三张物化视图。一个存储 了不同时间不同销售员的售卖量一个存储了不同时间不同门店的销售量以及每个销售员的总销售量。 当查询 7 月 19 日各个销售员都买了多少钱的话。就可以匹配 mv_1 物化视图。直接 对 mv_1 的数据进行查询。3查询自动匹配自动改写的原则4最优路径选择1依据必要条件选择候选集合决策出可用的物化视图2基于候选集合判断哪个聚合程度更高前缀索引能匹配上。从表结构上去看物化视图表日期是一个排序列同时聚合程度MV1明显比base高最后选择MV1作为查询条5查询改写SELECT seller_id, sum(sale_amt) FROM sales_records WHERE sale_date 2020-07-19 GROUP BY seller_id;改写之后SELECT seller_id, sum(sale_amt) FROM mv_1 WHERE sale_date 2020-07-19 GROUP BY seller_id;6使用限制1目前支持的聚合函数包括常用的sum、min、max、count以及计算pv、uv、留存率等常用去重算法hll_union和用于精确去重计算count(distinct)的算法bitmap_union。2物化视图的聚合函数的参数不支持表达式仅支持单列例如sum(ab)不支持。3使用物化视图功能后由于物化视图实际上损失了部分维度数据对表的 DML 类型操作会有一些限制如果表的物化视图 key 中不包含删除语句中的条件列则删除语句不能执行。例如想要删除渠道为 app 端的数据由于存在一个物化视图并不包含渠道这个字段则该删除不能执行因为删除在物化视图中无法被执行。此时只能先删除物化视图删除完数据后重新构建新的物化视图。4单表上过多的物化视图会影响导入的效率导入数据时物化视图和 base 表数据是同步更新的如果一张表的物化视图表超过 10 张则有可能导致导入速度很慢。这就像单次导入需要同时导入 10 张表数据是一样的。5相同列不同聚合函数不能同时出现在一张物化视图中比如select sum(a), min(a) from table不支持。6物化视图针对 Unique Key 数据模型只能改变列顺序不能起到聚合的作用所以在 Unique Key 模型上不能通过创建物化视图的方式对数据进行粗粒度聚合操作。三演示案例1案例一创建一个 Base 表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);插入数insert into sales_records values(1,2,3,2020-02-02,10);基于这个 Base 表的数据提交一个创建物化视图的任务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 test_db; (Version 0.13)查看 Base 表的所有物化视图desc sales_records all;desc sales_records all; 查看表下的所有物化视图检验当前查询是否匹配到合适的物化视图EXPLAIN SELECT store_id, sum(sale_amt) FROM sales_records GROUP BY store_id;0:VOlapScanNode(143) TABLE: test_db.sales_records(store_amt), PREAGGREGATION: ON partitions1/1 (sales_records) tablets10/10, tabletList16033,16035,16037 ... cardinality1, avgRowSize0.0, numNodes1 pushAggOpNONE0:VOlapScanNode(143)TABLE:test_db.sales_records(store_amt), PREAGGREGATION: ONpartitions1/1 (sales_records)tablets10/10, tabletList16033,16035,16037 ...cardinality1, avgRowSize0.0, numNodes1pushAggOpNONE删除物化视图语法DROP MATERIALIZED VIEW 物化视图名 on Base表名;2案例二1. 创建 Base 表create table advertiser_view_record( time date, advertiser varchar(10), channel varchar(10), user_id int ) distributed by hash(time) properties(replication_num 1);插入数据insert into advertiser_view_record values(2020-02-02,a,app,123);2. 创建物化视图create materialized view advertiser_uv as select advertiser, channel, bitmap_union(to_bitmap(user_id)) from advertiser_view_record group by advertiser, channel;说明Doris 中count(distinct)与bitmap_union_count结果一致后者等于对bitmap_union结果求count。涉及count(distinct)的查询创建bitmap_union聚合的物化视图可加速。user_id为INT类型需通过to_bitmap转换为bitmap类型后才能进行bitmap_union聚合。3. 查询自动匹配SELECT advertiser, channel, count(distinct user_id) FROM advertiser_view_record GROUP BY advertiser, channel;该查询会自动匹配物化视图advertiser_uv无需手动指定视图名。SELECT advertiser, channel, bitmap_union_count(to_bitmap(user_id)) FROM advertiser_uv GROUP BY advertiser, channel;4检验是否匹配到物化视图EXPLAIN SELECT advertiser, channel, count(distinct user_id) FROM advertiser_view_record GROUP BY advertiser, channel;0:VOlapScanNode(143)TABLE:test_db.advertiser_view_record(advertiser_uv), PREAGGREGATION: ONpartitions1/1 (advertiser_view_record)tablets10/10, tabletList16126,16128,16130 ...cardinality0, avgRowSize0.0, numNodes1pushAggOpNONE在EXPLAIN的结果中首先可以看到OlapScanNode的 rollup 属性值为advertiser_uv。也就是说查询会直接扫描物化视图的数据说明匹配成功。其次对于user_id字段求count(distinct)被改写为求bitmap_union_count(to_bitmap)也就是通过bitmap的方式来达到精确去重的效果。3案例3用户的原始表有 (k1,k2,k3) 三列。其中 k1,k2 为前缀索引列。这时候如果用户查询条件中包含 where k11 and k22 就能通过索引加速查询。但是有些情况下用户的过滤条件无法匹配到前缀索引比如 where k33。则无法通过索引加速查询。创建以 k3 作为第一列的物化视图就可以解决这个问题。1查询explain select record_id,seller_id,store_id from sales_records where store_id3;2创建物化视图create materialized view mv_1 as select store_id, record_id, seller_id, sale_date, sale_amt from sales_records;通过上面语法创建完成后物化视图中既保留了完整的明细数据且物化视图的前缀索引为 store_id 列。3查看表结构desc sales_records all;4查询匹配explain select record_id,seller_id,store_id from sales_records where store_id3;这时候查询就会直接从刚才创建的 mv_1 物化视图中读取数据。物化视图对 store_id 是存在前缀索引的查询效率也会提升。