写点什么

十八般武艺玩转 GaussDB(DWS) 性能调优:路径干预

发布于: 2021 年 01 月 13 日

摘要:路径生成是表关联方式确定的主要阶段,本文介绍了几个影响路径生成的要素:cost_param, scan 方式,join 方式,stream 方式,并从原理上分析如何干预路径的生成。


一、cost 模型选择


顾名思义,cost_param 是控制 cost 相关的一个参数。在了解 cost_param 之前,先回顾一下选择率的概念,GaussDB 优化器中的选择率是指,当一个表有一个过滤或关联条件时,通过该条件能被选中的行数占总行数的比例,是介于 0~1 之间的一个实数。选择率在优化器中是一个重要的概念,主要应用于行数和 distinct 值的估算,行数和 distinct 值是计划生成中的基本要素。


首先,我们来看带有过滤条件的基表行数如何估算。如果一个表只有一个过滤条件,那么以选择率乘以表的行数,即可得到过滤完的行数;如果有多个过滤条件,那么就需要算出一个综合的选择率,如何计算?方式有二:一是通过多列统计信息直接计算,二是通过组合单列的选择率。那么组合的方式就由参数 cost_param 决定了,具体地,



举一个例子,TPC-H 1x 的 part 表,过滤条件是:p_brand = 'Brand#45' and p_container = 'WRAP CASE',查看不同 cost_param 下的过滤后行数。


(1)cost_param=0



(2)cost_param=2



从估算出的行数(E-rows)和实际的行数(A-rows)对比可以看出,cost_param=0 的不相关模型适合 part 表的 p_brand 和 p_container 列。


其次,Join 的行数怎么估算的呢?原理跟过滤条件的行数估算是类似的,如果没有多列统计信息可以使用,则也需要单独计算每个条件的选择率,然后计算出综合选择率,得出行数。例如 TPC-H 1x lineitem 和 orders 关联,关联条件是:l_orderkey = o_orderkey and o_custkey = l_suppkey,不同 cost_param 的执行情况如下:


(1)cost_param=0



(2)cost_param=2



此例中,Join 的列之间也适合完全相关模型,这与 l_orderkey 和 l_suppkey 的分布是吻合的。


由于 TPC-H 的模型接近完全不相关模型,因此 cost_param=0 模型可以较好的描述场景,实际应用中,用户可以根据具体业务场景来调整模型,行数估算的准确性是计划生成的重要保证,在调优中检查估算的最直接的地方。GaussDB 会在后续版本中新增更多的模型供业务需求选择。


二、Scan 方式的选择


GaussDB 中扫描方式主要分顺序扫描和索引扫描,每种扫描方式都对应若干扫描算子,顺序扫描在行列存中对应的扫描算子分别是 Seq Scan 和 CStore Scan 算子(下面我们讨论中不加区分)。这些扫描算子大部分都可以通过开关来进行调控,例如 Seq Scan,如果设置 enable_seqscan=off,则表示不会优先选择 Seq Scan,而不是一定不会选。扫描方式的选择,很大程度上决定了获取基表数据的路径。我们以如下的例子来说明:


select l_orderkey, o_custkey from lineitem, orders where l_orderkey =

o_orderkey;


lineitem 分布键是 l_orderkey,并且在 l_orderkey 上有 index,orders 分布键是 o_orderkey。默认情况下,Scan 的方式如下:



两个表都是顺序扫描的路径,关联方式选择了 Hash Join。如果把 Seq Scan 关掉(enable_seqscan=off),计划如下:



lineitem 的扫描变成了 Index Only Scan(因为 l_orderkey 的类型是 int),而在 orders 表上仍然选择 Seq Scan(因为没有其他路径),同时关联方式也变为了 Nest Loop,因为 Hash Join 需要全表扫描数据(lineitem 的 Seq Scan 已经被关掉了)。优化器的选择方式我们从代价(E-costs)一栏中也可以看出。再把 Index Only Scan 关掉,看看计划如何变化:



扫描路径都变为了 Seq Scan,而且 Seq Scan 的代价都很大。此时既然都走了 Seq Scan,为什么不选 Hash Join 呢,把 Nest Loop 关掉,看看 Hash Join 计划的代价:



从代价上看出 Hash Join 的总代价比 Nest Loop 的小,但优化器没有选择 Hash Join,这是因为优化器比较路径代价时,会比较 Startup 和 Total 代价,即启动代价和总代价,综合考虑,E-costs 栏中显示的是总代价。把 explain_perf_mode 设置为 normal,查看原 Nest Loop 的启动代价:



红框中的两个 cost,分别是启动代价和总代价,在看 Hash Join 的 cost,明显 Hash Join 的启动代价比 Nest Loop 的大很多(启动代价代表了输出第一条数据的代价),优化器在比较路径时,综合了这两个代价,最终推荐了 Nest Loop 的路径。



从上面的例子可以看出,扫描路径的调控,可以改变路径生成,合理的搭配是生成最优计划的前提,默认情况下,GaussDB 优化器可以根据现有的路径选择(如上面的 lineitem 有两条扫描路径,orders 只有一条扫描路径),最后确定出最优的一条。两条路径代价比较时,总代价不是唯一要素,但总代价越小,一般也会越容易被选中。


三、关联方式的选择


GaussDB 优化器中表关联的主要方式有:Nest Loop,Hash Join 和 Merge Join,分别可以通过 enable_nestloopenable_hashjoinenable_mergejoin 进行控制,这种控制也不是绝对的,可以理解为是否优先选择。大部分场景下,三种路径的代价关系:Hash Join < Merge Join < Nest Loop。我们以一个简单的关联示例说明,store_returns 和 store_sales 是 TPC-DS 1x 中两个表,SQL 如下:


select count(*) from store_returns, store_sales where sr_customer_sk =

ss_customer_sk;


默认情况下,优化器推荐 Hash Join 路径,计划如下:



如果把 Hash Join 关掉,则优化器选择了 Merge Join 路径:



如果再把 Merge Join 路径关掉,可能就会选择 Nest Loop 路径。关联方式的控制开关一般用于调优或规避问题,但具体是否能够起作用要看具体的语句,除了当前关联方式,还有没有其他方式。实际场景中,一个语句中关联的算子较多,一般很难用参数 enable_hashjoin 或 enable_nestloop 或 enable_mergejoin 来控制某两个表的 Join 方式,GaussDB 中更细致的语句级别的调优手段是 Plan Hint,感兴趣的读者可以参考产品手册。


四、Stream 方式的选择


Stream 算子是 GaussDB 分布式执行的关键算子之一,主要起到网络传输的作用,概要介绍可以参考:GaussDB(DWS)性能调优系列实战篇一:十八般武艺之总体调优策略。Stream 算子由参数 enable_stream_operator 控制,如果关掉 Stream 算子,则可能导致生成不下推的计划,例如:



因为 lineitem 表关联的键 l_partkey 不是 lineitem 的分布键,需要添加 Stream 算子,但 Stream 功能被禁,于是只能生成不下推计划。


GaussDB 计划中常见的主要 Stream 算子包括 Redistribute、Broadcast 和 Gather。Gather 一般是分布式计划中,CN 用于收集 DN 的数据进行最后的处理,除非最后收集的行数非常多,这个算子涉及性能问题一般较少。Redistribute 和 Broadcast 一是对“互补”的算子,前者用于重分布,后者用于广播,生成计划时,优化器会根据代价大小来选择。当 Join Key 没有包含表的分布键的时候,一般会添加 Redistribute 路径,能选择 Redistribute 路径理论上也可选择 Broadcast 路径,最终选择哪条路径要看优化器估算的代价是多少。这两个算子可以通过参数 enable_redistribute 和 enable_broadcast 进行控制。


在 SMP 开启的情况下,当并行度(dop)大于 1 时,一般还会有 Local Redistribute、Split Redistribute、Local Broadcast 和 Split Broadcast;当倾斜优化开启时,还有 PART REDISTRIBUTE PART ROUNDROBIN、PART_REDISTRIBUTE_PART_BROADCAST、PART_REDISTERIBUTE_PART_LOCAL 等等,这些也是 Stream 算子,主要就是重分布、广播、RoundRobin 的一些扩展形式,这里我们不一一介绍了,感兴趣的读者可以参考 GaussDB DWS 产品手册。


我们考虑两个表的简单关联,store_sales 和 sr_tbl,它们的分布键分别是 ss_item_sk 和 sr_returned_date_sk,Join 条件是 store_sales.ss_customer_sk =sr_tbl. sr_customer_sk,执行结果如下:



由于两个表的分布键都不是 Join Key,因此走 Hash Join 路径的话需要有一个表做 Broadcast 或者两个表都做 Redistribute,但是 store_sales 表比较大(E-rows 显示 28.7 亿行),而 sr_tbl 表行数估算比较少(E-rows 显示 100 行),优化器认为适合做 Broadcast。于是最终选择了一边 Broadcast 的计划。


对于这个计划,由于 sr_tbl 表统计信息不准确(如果是中间结果集,则表示中间结果集估算不准),一种调优的方法是,将 sr_tbl 的表统计信息重新收集准确一些(如果 sr_tbl 是中间结果集,则无法收集),另一种方法是让 sr_tbl 走 Redistribute 路径,而后者我们又有两种方式来实现,一是用 Plan Hint,即在生成计划时,告诉优化器走 Redistribute 路径,二是把 Broadcast 关掉。禁用 Broadcast 后,执行计划如下:



本列中,开启了 SMP 自适应,即优化器会根据系统资源和当前 Active SQL 数量来自行决定并行度(dop),如果 Redistribute 和 Broadcast 选择不当,则可能导致


(1)Broadcast 计划会出现下盘


(2)两个计划的并行度不一样,最终执行时间可能会差异比较大。


对于 Stream 方式的控制,一般的调优方式有 Plan Hint、GUC 参数、改善统计信息或估算信息。


五、结束语


本文介绍的 cost_param 属于 cost 底层参数,建议对数据特征和使用场景比较熟悉的 DBA 慎重使用。Scan、Join、Stream 调控的基本依据也是代价,代价一般体现在执行耗时上,调优时可从 Performance 中识别出性能的瓶颈点,分析选择的算子是否与代价匹配。另外,除了本文介绍的 Session 级别的控制参数外,还有基表、中间结果的行数,也可以通过 Plan Hint 进行语句级别的调控,感兴趣读者可通过 GaussDB DWS 产品文档进一步了解。


本文分享自华为云社区《GaussDB(DWS)性能调优系列实战篇五:十八般武艺之路径干预》,原文作者:- 大道至简 - 。


点击关注,第一时间了解华为云新鲜技术~


发布于: 2021 年 01 月 13 日阅读数: 23
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
十八般武艺玩转GaussDB(DWS)性能调优:路径干预