暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Doris查询太慢?看一下这些优化方法...

大数据技能圈 2023-10-30
348

Doris数据查询技巧全在这儿了介绍了Doris的查询语句,这可以帮助没有开发经验的朋友快速入门,掌握SQL语句的基本要领。本章将从SQL语句执行过程和执行原理出发,探讨查询优化的方式。

1

执行计划

执行计划是指SQL语句执行过程的拆分动作,将复杂过程分解成若干简单的操作,并按照数据分布情况匹配索引,根据优化规则调整执行顺序。数据库的执行计划决定了查询性能。

Doris的执行计划由FE生成。Doris的执行计划分为两个阶段:第一个阶段是通过执行SQL语句生成PlanNodeTree,也就是把SQL语句转化成具体的查询算子,包括OlapScanNode、JoinNode、SortNode、AggregationNode等,如图7-1所示。

第二阶段把PlanNodeTree转化成分布式执行计划,即PlanFragmentTree,如图7-2所示。这个过程是把每个查询算子(也可以理解为计算步骤)按照数据的分布生成分布式执行计划。这个执行计划包括数据的传输和汇总。数据的传输由DataSink和ExchangeNode配合完成,其中DataSink负责扫描本地数据并发送出去,ExchangeNode负责接收数据并临时存储。

查看执行计划有3种命令:Desc Graph、Explain和Desc verbose。Desc Graph主要是通过图形化的方式展示执行计划的概要,Explain展示数据过滤条件、执行步骤等更多信息,Desc Verbose展示执行哪些列的信息。以如下查询为例,我们分别展示通过3种命令查看执行计划的结果。


图7-1 Doris将SQL转化成逻辑执行计划

图7-2 Doris逻辑执行计划转换成分布式执行计划

通过Desc Graph命令查看到的执行计划共有83行,截图如图7-3所示。

图7-3 通过Desc Graph命令查看到的执行计划截图

从图7-3中我们可以很形象地看出每一个步骤的操作类型、读取的数据、对应的上一个阶段。本次查询涉及的算子有OlapScanNode、DataStreamSink、Exchange、Hash Join、Aggregate、Top1 N、ResultSink等。

通过Explain命令查看到的执行计划显示了更多细节,但是没有通过Desc Graph命令查看到的那么直观,截图如图7-4所示。

图7-4 通过Explain命令查看到的执行计划截图

这里的执行计划很详细地展示了每一步操作的数据、数据类型、分区、数据集大小、数据所在节点信息、是否命中索引及ROLLUP等信息。

通过Desc verbose命令查看到的执行计划太长,这里只截取第一步,如图7-5所示。

图7-5 通过Desc verbose命令查看到的执行计划部分截图

通过Desc verbose命令查看到的执行最详细,展示了每一步需要读取的字段。这也导致展示的内容中有很多重复。

执行计划的生成是从上到下的,但是执行是从下到上进行的,由最末端的叶子节点读取磁盘数据,中间的算子进行聚合计算和数据重分布,最后汇总结果到顶端的FE节点并反馈给用户,如图7-6所示。

图7-6 执行计划的执行示意图

2

查询优化器

本节将按照SQL语句的执行步骤详细解析查询优化器。查询优化器是数据库管理系统针对用户请求进行内部优化,生成(或重用)执行计划并传输给存储引擎操作数据,最终返回结果给用户的组件。查询优化器是数据库管理系统的核心组件之一,决定对特定的查询使用哪些索引、关联算法,从而高效运行。它是优化器中最重要的组件之一。从查询优化器的定义我们可以看出,查询优化是在FE节点实现的。

查询优化器的作用是在不改变查询结果的前提下,找到一个最优的执行计划。一个典型的查询优化示例如图7-7所示。

图7-7 查询优化示例

优化前查询需要执行多次OR操作,查询时间13s,优化后只需要0.94s,但是优化前后查询结果是不变的。类似的优化还有很多。

查询优化器的运作主要分为5部分。

(1)词法、语法解析

词法解析是指SQL语句中的关键词解析。关键词也叫Key Word,是查询语句的骨架,包括SELECT、FROM、JOIN、ON、GROUP BY、ORDER BY等。

词法解析之后就是语法解析,也就是判断SQL语句是否符合语法要求,例如GROUP BY必须在ORDER BY的前面,HAVING必须在GROUP BY后面。

通过语法解析和词法解析,系统可以得到一个抽象语法树,如图7-8所示。

图7-8 抽象语法树

(2)语义解析

语义解析最关键的步骤是检查元数据校验语义准确性。元数据包括表名、列名以及列类型。只有通过这一步的检查,才可以正确执行SQL语句。语义解析过程如图7-9所示。

图7-9 语义解析过程

(3)Query改写

Query改写一般是根据查询优化器内置的规则进行改写,包括表达式改写和子查询改写。

表达式改写的程序入口是ExprRewriter类,这个类的apply方法会逐个根据规则进行表达式匹配和改写。

子查询改写是针对一些复杂的查询进行改写,例如把IN语句改写成LEFT SEMI JOIN语句。

(4)单机执行规划

子查询改写以后得到初步优化的SQL语句,这时还可以进行两种优化:Join Order、谓词下推。

Join Order优化的案例如图7-10,调整JOIN顺序可以极大地提升查询效率。在默认顺序下,t1和t2进行关联,可能会产生百亿级别的表或者内存溢出,但是如果t1先和t3关联,再和t2关联,则可以快速出结果。

图7-10 Join Order优化案例

oin Order优化是每次查询都找产生中间结果最小的表进行合并,如图7-11所示。Join Order在多表关联场景的优化效果是非常显著的。

图7-11 Join Order优化过程

谓词下推是查询优化中最常见的方法,是把查询条件或者关联条件放到JOIN操作之前,先过滤无效数据,再执行关联操作。

(5)根据单机执行计划生成分布式执行计划

经过多次改写执行计划,结合数据的物理分布,系统就可以生成分布式执行计划。单机执行计划改写成分布式执行计划,最大的不同就是JOIN操作,这里主要有Broadcast Join、Shuffle Join、Bucket Shuffle Join和Colocate Join。这四种Join操作的差异可以回看6.2节。4种Join操作各有应用场景,也有使用限制,我们需要尽可能引导数据库按照最优的JOIN操作方式实现数据关联。最优选择Colocate Join,次之Bucket Shuffle Join,然后是在Broadcast Join和Shuffle Join之间选择一个消耗最小的方式。是否命中Colocate Join可以通过执行计划看出,如图7-12所示。

图7-12 Colocate Join应用示例

分布式执行计划还有一个优化点就是分布式聚合。常规的聚合方式是先将明细数据按照分组字段进行重分布,让分组字段Key值相同的数据分布到相同节点以后在各个节点进行数据汇总,然后返回汇总数据给主节点进行合并输出。Doris引入了两阶段聚合概念:先在本地对数据进行汇总,然后根据Hash值进行数据重分布,这样减少了网络数据传输,也提高了第二阶段数据汇总的速度,最终实现了查询提速,如图7-13所示。

图7-13 两阶段聚合示意图

通过对查询优化器的解析,我们知道数据库已经主动对查询请求进行了大量优化,其中我们可以调整的主要是Join Order和分布式Join操作方法。接下来,我们还将从索引、物化视图、ROLLUP等方面进一步推动SQL查询的优化。

3

索引

索引的作用是帮助快速过滤或查找数据。这样说可能有点抽象,我们小时候都有过查字典的经历,一般是先根据首字母找字典的“汉语拼音音节索引表”(见图7-14),找对应汉字所在的页码,然后在具体的页码查看展开内容。

图7-14 汉语拼音音节索引

数据库的索引也具有类似功能,只不过一般关系型数据库的索引是用Key字段来构建,便于快速定位到指定的记录。而OLAP型数据库索引略有不同。

目前,Doris主要支持两类索引:一类是内建的智能索引,包括前缀索引和ZoneMap索引;另一类是用户创建的二级索引,包括Bloom Filter索引和BITMAP索引。

其中,ZoneMap索引是在列存储格式上对每一列索引信息自动维护,包括Min、Max、Null值个数等。这种索引对用户透明,且无法优化和调整,因此这里就具体介绍。下面主要介绍其他三类索引。

01

前缀索引

本质上,Doris的数据存储在类似SSTable(Sorted String Table)的数据结构中。该结构是一种有序的数据结构,可以按照指定的列进行排序存储。在这种数据结构上,以排序列为条件进行查找非常高效。

在Aggregate、Unique和Duplicate三种数据模型中,底层的数据存储是对各自建表语句中AGGREGATE KEY、UNIQUE KEY和DUPLICATE KEY指定的列进行排序存储的。前缀索引是一种在排序的基础上根据给定前缀列,快速查询数据的索引方式。


前缀索引是以Block为粒度创建的稀疏索引,一个Block包含1024行数据,每个Block以第一行数据的前缀列的值为索引。建表时,建议将查询中常见的过滤字段放在Schema的前面,区分度越大,查询频次越高的字段越往前放。

我们将一行数据的前36 B作为这行数据的前缀索引,当遇到VARCHAR类型数据时,即使没有达到36字节前缀索引会直接截断,举例说明。

表7-1的前缀索引为user_id(8 B)+age(4 B)+message(prefix 20 B)。

表7-1 前缀索引示例一

表7-2的前缀索引为user_name(20 B),即使没有达到36 B,因为遇到VARCHAR类型数据,所以直接截断,不再继续增加字段。

表7-2 前缀索引示例二

查询条件是前缀索引的前缀,可以极大地加快查询速度。比如在示例一中,执行如下查询:

该查询的效率会远高于如下查询:

所以在建表时,正确选择列顺序能够极大地提高查询效率。

02

Bloom Filter索引

用户可以在建表时指定在某些列上创建Bloom Filter索引(以下简称BF索引),也可以在运行时通过ALTER TABLE命令新增BF索引。建表时,BF索引创建是通过在建表语句的PROPERTIES里加上bloom_filter_columns参数来实现的,支持多个字段组合。通过ALTER TABLE命令增加BF索引的语句如下:

BF索引本质上是一种位图结构,用于快速判断一个给定的值是否在一个集合中。这种判断会产生小概率误判,即如果返回False,则一定不在这个集合中,如果返回True,则有可能在这个集合中。

BF索引也是以Block为粒度创建的。每个Block中指定列的值作为一个集合生成一个BF索引条目,用于在查询时快速过滤不满足条件的数据。BF索引比较适合创建在高基数列上,比如UserID,如果创建在低基数列上,比如“性别”列,每个Block几乎都会包含所有取值,导致索引失去意义。

03

BITMAP索引

用户可以在建表时指定在某些列上创建BITMAP索引,也可以在建表后通过ALTER TABLE或者CREATE INDEX命令新增BITMAP索引,语句如下:

BITMAP索引是一种特殊的数据库索引技术,使用bit数组进行数据存储。位置编码中的每一位表示键值对应的数据行的有无。一个位图指向的是几十甚至成百上千行数据的位置。

相对于B*Tree索引,BITMAP索引占用的空间非常小,创建和查询速度非常快。当根据键值做查询时,数据库可以根据BITMAP索引快速定位到具体的行。而当根据键值做and、or或in查询时,数据库直接用索引的位图进行与、或运算,快速获得结果行数据。

Doris中的BITMAP索引仅支持在单列上创建,并且只支持定长类型的字段查询,不支持TEXT或者STRING类型的字段查询。BITMAP索引适合低基数列查询场景,建议在100到100000之间,如职业、地市等。基数太高,BITMAP索引则没有明显优势;基数太低,BITMAP索引空间效率和性能会大大降低。

对于特定类型的查询,例如count、or、and等逻辑操作,只需要进行位运算,如类似SELECT count(*)FROM table WHERE city='beijing' AND job='teacher'这种多个条件组合的查询场景,如果在每个查询条件列上都建立BITMAP索引,则可以进行高效的位运算,精确定位到需要的数据。筛选出的结果集越小,BITMAP索引的优势越明显。

4

物化视图

顾名思义,物化视图是物理化的视图,即保存查询结果数据的视图。物化视图结合了表和视图的优点,既支持动态刷新数据,又可以满足高效查询需求。

物化视图是一种以空间换时间的数据分析技术。Doris支持在基础表之上建立物化视图,比如可以在明细数据模型表上基于部分列建立聚合视图,这样可以同时满足对明细数据和聚合数据的快速查询。同时,Doris能够保证物化视图和基础表的数据一致性,并且在查询时自动匹配合适的物化视图,极大地降低数据维护成本,为用户提供一致且透明的查询服务。

用户需要根据查询语句的特点来决定创建的物化视图。这里并不是说物化视图定义和某个查询语句一模一样就是最好,而是有两个原则。

第一,从单表的大量查询语句中抽象出频率较高的分组和聚合方式作为物化视图的定义。如果抽象出来的物化视图可以被多个查询匹配到,这张物化视图就很好。如果物化视图只和某个特殊的查询很匹配,而不能匹配到其他查询,这张物化视图的性价比不高,既占用了集群的存储资源,还不能为更多的查询服务。所以,用户需要结合自己的查询语句,以及数据维度信息抽象出一些物化视图的定义。

第二,不需要对所有维度组合都创建物化视图。实际的分析查询并不会覆盖所有维度组合,所以给常用的维度组合创建物化视图即可,从而达到空间和时间上的平衡。

我们可通过CREATE MATERIALIZED VIEW命令创建物化视图。创建物化视图是一个异步操作,也就是说用户成功提交创建任务后,Doris会在后台对存量数据进行计算,直到物化视图创建成功。CREATE MATERIALIZED VIEW命令如下:

从命令模板可以看出,物化视图由视图名、查询和PROPERTIES键值对三部分组成。物化视图也是数据库的一个对象,命名必须全局唯一。查询只能是基于单表的简单查询,不支持JOIN操作和字段表达式查询。PROPERTIES键值对用于声明物化视图的一些配置、选填项。目前,Doris仅支持timeout参数,用于设定物化视图构建的超时时间。

1)物化视图支持的查询模板如下:

物化视图的查询有以下限制。

❑查询涉及的所有列只允许使用一次。

❑仅支持不带表达式计算的单列查询,不支持CASE WHEN处理,也不支持函数处理。

❑GROUP BY子句非必需。

❑ORDER BY子句指定列的顺序必须和查询字段顺序一致。

❑如果不声明ORDER BY,Doris根据规则自动补充排序列。如果物化视图是聚合类型,所有的分组列自动补充为排序列。如果物化视图是非聚合类型,前36 B自动补充为排序列。如果自动补充的排序列小于3个字段,前3个字段作为排序列。

❑查询的基础表必须是单表,且不能放在子查询中。

目前,物化视图创建支持的聚合函数有sum、min、max、count、bitmap_union、hll_union六种。其中,bitmap_union的形式可为bitmap_union(to_bitmap(column))或者bitmap_union(column),前者适合基础表(在Doris中,我们将用户通过建表语句创建的表称为基础表)为Duplicate模型的场景,后者适合基础表为Aggregate模型或者Unique模型的场景。hll_union的使用规则和bitmap_union一致。物化视图中聚合和查询中聚合的匹配关系如表7-3所示。

表7-3 物化视图中聚合和查询中聚合的匹配关系

其中,Bitmap和HLL的聚合函数在匹配到物化视图后,查询的聚合算子会根据物化视图的表结构进行一次改写。

为了保证物化视图中的数据和Base表中的数据一致,Doris会将导入、修改、删除等对Base表的操作同步到物化视图中,并且通过增量更新的方式提高更新效率。我们可以通过事务的方式保证原子性,比如如果用户通过INSERT命令插入数据到Base表,这条数据会同步插入物化视图。当在Base表和物化视图均写入成功后,INSERT命令才会成功返回。

物化视图创建成功后,查询不需要改变,也就是还是查询Base表。Doris会根据当前查询语句自动选择一个最优的物化视图,从物化视图中读取数据并计算。

假设用户有一张销售明细表,表中存储了每个交易的用户id、销售员、售卖门店、销售时间以及金额。建表语句为:

用户如果需要经常对不同门店的销售量进行分析查询,则可以针对sales_records表创建一张以门店分组,对相同门店销售额求和的物化视图,创建语句如下:

返回成功,则说明创建物化视图任务提交成功。

由于创建物化视图是一个异步操作,用户在提交创建物化视图任务后,需要异步地通过命令检查物化视图是否创建完成(如图7-15所示),命令如下:

图7-15 查看物化视图是否创建完成

当创建完成物化视图后,用户再查询不同门店的销售量时,就可直接从创建的物化视图store_amt中读取聚合数据,提高了查询效率。

用户再查询时依旧指定查询sales_records表,比如:

上面查询就能自动匹配到store_amt物化视图。用户可以通过EXPLAIN命令检查当前查询是否命中合适的物化视图,如图7-16所示。

图7-16 查询是否命中合适的物化视图

物化视图还适合不进行数据聚合的场景,以及补充前缀索引无法实现的场景,示例如下:

这个视图等于针对sales_detail表新建了基于sale_date、store_id、seller_id三个字段的稀疏索引,在查询条件是sale_date和store_id时,大大提升查询性能。

5

ROLLUP

ROLLUP在多维分析中是“上卷”的意思,即将数据按某种指定的粒度进行进一步聚合。Base表中保存着按用户建表语句指定的方式存储的基础数据。在Base表之上,我们可以创建任意多个ROLLUP表。这些ROLLUP表中的数据是基于Base表产生的,并且在物理上是独立存储的。ROLLUP表的基本作用是在Base表的基础上,获得更粗粒度的聚合数据。

在Doris中,ROLLUP是先于物化视图诞生的,但是为什么把物化视图放到前面讲呢,主要是为了方便读者理解。物化视图早在Oracle数据库中已经有概念普及。在没有物化视图功能之前,用户一般使用ROLLUP通过预聚合方式提升查询效率。但是ROLLUP具有一定的局限性,不能基于明细模型做预聚合。

物化视图在覆盖了ROLLUP功能的同时,还能支持更丰富的聚合函数,所以物化视图其实是ROLLUP的一个超集。表7-4简单说明一下二者的功能差异。

表7-4 ROLLUP和物化视图对比

在Duplicate模型中,ROLLUP已经失去“上卷”这一层含义,而仅仅用于重定义排序列,以便查询语句命中前缀索引。在前文中,我们介绍了前缀索引的概念,并且认识到对于一张Base表只能根据其定义的Key字段创建前缀索引。对于以没有命中前缀索引的列为条件的查询来说,效率可能无法满足需求。因此,我们可以通过创建ROLLUP来人为调整列顺序。

例如针对7.4节的案例,我们可以通过ROLLUP语句,达到和使用物化视图相同的效果。

在Aggregate和Unique模型中,ROLLUP才有上卷的作用。ROLLUP在Base表的基础上新增不同维度的数据聚合,以快速响应不同维度的查询。

针对聚合模型(Unique模型是Aggregate模型的特例),用户可以在建表时定义ROLLUP:

也可以在表创建完以后添加ROLLUP语句:

建表完以后,用户可以通过DESC example_db.rollup_index_table ALL;语句查看表的基本信息和所有ROLLUP信息,如图7-17所示。

图7-17 查看表的基本信息和所有ROLLUP信息

最后,Doris官网关于ROLLUP的几点说明,对我们使用ROLLUP有很大帮助。

❑ROLLUP最根本的作用是提高某些查询的效率(无论通过聚合来减少数据量,还是修改列顺序以匹配前缀索引)。因此,ROLLUP的含义已经超出“上卷”的范围。这也是源代码中将其命名为Materialized Index(物化索引)的原因。

❑ROLLUP表附属于Base表,可以看作是Base表的一种辅助数据结构。用户可以在Base表的基础上,创建或删除ROLLUP表,但是不能在查询中显式地指定查询某ROLLUP表。是否命中ROLLUP表完全由Doris查询引擎决定。

❑ROLLUP表中的数据是独立存储的。因此,创建的ROLLUP表越多,占用的磁盘空间也就越大,同时对导入速度也会有影响,但是不会降低查询效率。

❑ROLLUP表中的数据更新与Base表中的数据更新是完全同步的,无须用户关心这个问题。

❑ROLLUP表中列的聚合方式与Base表中的完全相同。用户在创建ROLLUP表时无须指定,也不能修改。

❑查询引擎能否命中ROLLUP表的一个必要条件是,查询所涉及的所有列(包括select list和where中的查询条件列等)都存在于该ROLLUP表的列中。否则,查询引擎只能命中Base表。

❑某些类型的查询(如count(*))在任何条件下,都无法命中ROLLUP表。

❑可以通过EXPLAIN your_sql命令获得查询执行计划,在执行计划中,查看是否命中ROLLUP表。

6

向量化查询引擎

传统的数据库查询都是采用一次一元组的Pipleline模式。这样,CPU的大部分时间不是在真正地处理数据,而是在遍历查询操作树,导致CPU的有效利用率不高、指令缓存性能低和频繁跳转。从存储层面看,磁盘读写能力提升并没有CPU硬件计算能力提升得那么迅速。目前对于磁盘来说,顺序读取速率比随机读取速率要高。但是通常数据库中的很多数据更倾向于随机存放状态。另外,目前磁盘读写速率已经远远跟不上CPU处理数据速率了。

在这种背景下,“列存储+向量化执行引擎”就成了解决痛点的核心利器。首先,列存储是按照相同的列数据存放在一起的,数据类型相同,压缩比高;其次,由于OLAP的特点,单次查询读取的列数少,I/O总量低,数据读取时间大大缩短;最后,向量化查询引擎将串行执行变成并行执行,大大提升了CPU处理速率。

从字面意义上理解,向量化其实就是由一次对一个值进行运算,转化成一次对一组值进行运算。从CPU角度分析,现代CPU都支持将单个指令应用于多个数据的SIMD向量化计算。所谓向量化计算,就是利用SIMD指令进行运算,比如一个具有128位寄存器的CPU,可以一次性从内存中拉取4个32位数据,并且进行计算,再把该128位数据写回内存,这比一次执行一条指令快4倍。

图7-18是单指令执行和SIMD执行的对比。内存中有4个INT类型数据,而INT类型数据占32位,当没有SIMD支持时,需要重复4次从内存中加载数据,然后再做4次乘法计算,最后把结果写回内存,同样要做4次。如果有SIMD支持,一次就能载入多个连续的数据,这样就只有一次内存加载和一次计算,比如4个数据连续做一次乘法运算,然后得到4个结果并写到4个寄存器,最后写回内存,就完成了一次向量化的指令计算操作。这样的操作比传统的没有SIMD支持或者没有向量化支持的CPU能够快四倍。随着CPU本身的发展,大家常用的128位的SSE指令后面又多了256位AVX指令,包括英特尔现在最新的AVX2指令,寄存器的位数不断变长,所以向量化一组运算数据越来越多,效率越来越高。但这不是一个线性关系,不一定寄存器的位数越多,性能就线性提升,但是它能够保证一次对一组值的计算更快。

图7-18 单指令执行和SIMD执行的对比

从数据库角度分析与从CPU角度分析类似,传统数据库执行引擎是一行一行处理数据,一次扫描一行数据,然后做对应的判断、计算。而向量数据库会把对一个元组的操作,转化成一次对一组值的操作,内存中一个Batch的数据不再以行的形式存在,而是以列的形式存在,所有的算子都通过并行方式进行批量计算,从而提高计算速度。

7

查询优化总结

前面介绍了SQL语句的执行过程、索引、物化视图、ROLLUP,其实核心目的只有一个——让查询变得更快。通过前面SQL语句的执行过程,我们也看到了,查询优化器可以实现大部分场景的查询性能优化。但是在某些极端的查询条件下,我们还需要针对表做一些调整,才能达到满意的效果。这里总结一下我们可以采取的优化策略。

01

数据模型选择

目前,Doris数据模型分为3类:Aggregate模型、Unique模型和Duplicate模型。3种模型中的数据都是按Key进行排序。

Aggregate模型:Key相同时,新旧记录进行聚合,目前支持的聚合函数有sum、min、max、replace。Aggregate模型可以提前聚合数据,适合报表和多维分析业务。

Unique模型:Key相同时,新记录覆盖旧记录。目前,Unique模型实现上和Aggregate模型的replace聚合函数一样,二者本质上可以认为相同,适合有更新的分析业务。

Duplicate模型:只指定排序列,相同Key的记录同时存在,适合数据无须提前聚合的分析业务。

选择合适的模型是性能优化的第一步。

02

内存表

Doris支持把表数据全部缓存在内存中,以便加速查询。内存表适合数据行数不多的维度表的存储,示例如下:

03

Colocate Join

为了加速查询,分布相同的相关表可以采用相同的分桶列数量和Bucket数量。当两表的数据分布相同且关联字段包含分布键时,Doris可以自动将两表关联调整为Colocate Join,从而避免数据在集群中的传输,大幅提高查询性能。在特定条件下,我们还可以合理利用Bucket Shuffle Join和Broadcast Join来提升查询性能。

04

减少大宽表,优选星型模型

业务方建表时,为了和前端业务适配,往往不对维度信息和指标信息加以区分,而将Schema定义成大宽表。Doris对这类大宽表查询性能往往不尽如人意。建议用户尽量使用星型模型区分维度表和指标表。频繁更新的维度表可以放在内存中,而如果只有少量更新,可以直接放在Doris中。在Doris中存储维度表时,用户可对维度表设置更多的副本,提高查询效率。

05

分区和分桶

Doris支持两级分区存储:第一层为Range分区,第二层为Hash分桶。

1)Range分区:用于将数据划分成不同区间,逻辑上可以理解为将原始表划分成多个子表。在具体业务上,多数情况下会选择日期字段进行分区。使用日期进行分区有以下好处。

❑可区分冷热数据。

❑可使用Doris分级存储(SSD+SATA)功能。

❑删除数据更加迅速。

2)Hash分桶:根据Hash值将数据划分成不同的Bucket,也有以下注意要点:

❑建议以区分度大的列做分桶,避免出现数据倾斜问题。

❑为了方便数据恢复,建议单个Bucket空间不要太大,保持在10 GB左右,所以建表或增加分区时请合理考虑分桶的数量。同时,不同分区可指定不同的分桶数量。

❑不建议采用随机分桶的方式,建表时需要明确指定Hash分桶列。

06

稀疏索引和BF索引

Doris对数据进行有序存储,在数据有序存储的基础上建立稀疏索引。

稀疏索引选取Schema中固定长度的前缀作为索引内容。目前,Doris选取36 B的前缀作为索引内容。建表时建议将查询中常见的过滤字段放在Schema的前面,且区分度越大,频次越高的查询字段越往前放。这其中有一个特殊的地方,就是VARCHAR类型字段:VARCHAR类型字段只能作为稀疏索引的最后一个字段,索引会在VARCHAR处截断,因此VARCHAR类型字段如果出现在前面,可能索引的长度不足36 B。

对于site_visit表:

排序列有siteid、city、username,siteid所占字节数为4,city所占字节数为2,username所占字节数为32,所以前缀索引内容为“siteid+city+username”的前30 B。

除稀疏索引之外,Doris还提供BF索引。BF索引对区分度比较大的列过滤效果明显。

07

BITMAP索引

Doris支持采用BITMAP技术构建索引。索引能够应用在Duplicate模型的所有列和Aggregate、Unique模型的Key列上。BITMAP索引适合应用在取值空间不大的列,例如性别、城市、省份等信息列。

08

物化视图和ROLLUP

ROLLUP本质上可以理解为Base表的一个物化索引。建立ROLLUP表时可只选取Base表中的部分列作为Schema,Schema中的字段顺序可与Base表中的不同。

在下列情形中,用户可以考虑建立ROLLUP表。

Base表中数据聚合度不高,这一般是因为Base表中有区分度比较大的字段,此时用户可以考虑选取部分列建立ROLLUP表。对于上述site_visit表,siteid可能导致数据聚合度不高,如果业务方经常根据城市统计pv,可以建立一个只有city、pv的ROLLUP表。

Base表中的前缀索引无法命中,这一般是因为Base表的建表方式无法覆盖所有的查询模式,此时用户可以考虑调整列顺序建立ROLLUP表。对于session_data表:

除了通过visitorid分析访问情况外,用户还可以通过brower、province分析访问情形,单独建立ROLLUP表:

参考:Doris试试数仓实战:https://weread.qq.com/web/reader/e3432570813ab8029g019a69k6f4322302126f4922f45dec

点击蓝字

关注我们

文章转载自大数据技能圈,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论