1背景随着数据规模的不断扩大,用户SQL的执行时间越来越长。提出了新的挑战。随着云上数据库的蓬勃发展,越来越多的传统用户迁移到云端,享受云上弹性扩展的红利。但是随着业务的快速扩张,发现即使动态添加了很多资源,SQL的执行时间还是越来越慢,并没有达到资源投入的预期效果。显然,虽然增加了很多新的资源,但是这些资源并没有得到充分的利用。许多传统商业数据库,如Oracle、SQLServer等,都提供了对并行查询引擎的支持,以充分利用系统资源,加快SQL执行速度。影响。本文主要介绍基于代价并行优化和并行执行的云数据库并行查询引擎的关键问题和核心技术。2.如何并行化查询对于类似OLAP的查询,很明显通常是对大量数据的查询。数据量大是指数据远大于数据库的内存容量,大部分数据可能没有缓存在数据库的缓冲区中。但是在执行查询时必须动态加载到缓冲区中,这会造成大量的IO操作,而IO操作是最耗时的,所以首先要考虑的是如何加速IO操作。由于硬件限制,每个IO的耗时基本是固定的。虽然顺序IO和随机IO是有区别的,但是在SSD盛行的今天,两者的区别正在逐渐接近。那么有没有其他方法可以加速IO呢?显然,并行IO是一种简单易行的方法。如果多个线程可以同时发起IO,每个线程只读取一部分数据,这样就可以快速的将数据读到数据库中。在缓冲区中。并行读取数据的示意图如上图所示。每个工人代表一个线程。如果数据已经有分区,则每个线程可以读取一个分区;或者可以把所有的数据按照一个固定的大小分成几块,比如一个数据的分页大小,然后每个线程以Round-robinpoll的方式读取一个shard。这里需要注意的是,根据已有的partition分配给不同的worker可能会导致每个worker处理的数据不均匀,而按照Round-robin的方式轮询,如果分片设置比较小,相对容易make保证每个worker处理的数据是比较统一的。如果数据只是读入缓冲区而不是立即处理,那么数据就会因为缓冲区满而被换出,从而失去了加速IO的意义。因此,在并行读取数据的同时,必须同时对数据进行并行处理,这是并行查询加速的基础。传统的优化器只能生成串行执行计划。为了并行读取数据和并行处理数据,必须首先修改现有的优化器,使优化器能够生成我们需要的并行计划。比如选择哪些表或者哪些表可以并行读取,通过并行读取会带来足够的收益;或者哪些操作可以并行执行,并且可以带来足够的收益。并不是说并行转型就一定会受益。比如一个数据量不大的表,可能只有几行,如果也是并行读取的话,并行执行需要的多线程构建加上线程间数据同步的成本可能是远远大于得到的好处。一般来说,并行执行会需要更多的资源和时间,得不偿失。因此,查询计划的并行化必须是基于成本的,否则可能会导致更严重的性能下降问题。3如何选择并行扫描表选择并行扫描表是生成并行计划的重要依据。通过基于并行扫描成本的计算和比较,选择一个可以并行扫描的表作为候选表是并行执行计划迭代的第一步。基于新的并行成本,可能会有更好的JOIN顺序选择,尤其是当参与JOIN的表数较多时,需要更多的额外迭代空间。为了不让优化过程耗费太多时间,保持原计划的JOIN顺序为好。另外,对于每一个参与JOIN的表,由于表的访问方式不同,比如全表扫描、Ref索引扫描、Range索引扫描等,这些都会影响到最终并行扫描的开销。通常我们选择最大的表作为并行表,这样并行扫描的好处最大。当然也可以同时选择多个表进行并行扫描。更复杂的情况将在后面讨论。下面是查询前10个年消费用户的例子:SELECTc.c_name,sum(o.o_totalprice)assFROMcustomerc,ordersoWHEREc.c_custkey=o.o_custkeyANDo_orderdate>='1996-01-01'ANDo_orderdate<='1996-12-31'GROUPBYc.c_nameORDERBYsDESCLIMIT10;orders表是一张订单表,里面有很多数据。这种类型的表也称为事实表。customer表是数据比较少的客户表。这种表也称为维度表。那么这条SQL的并行执行计划如下图所示:从计划中可以看出,会并行扫描orders表,由32个工作线程执行。根据o_custkey做索引查找进行JOIN,JOIN的结果发送给一个收集器组件,然后收集器组件继续执行后续的GROUPBY、ORDERBY和LIMIT操作。4、选择多表并行JOIN并行扫描一张表后,你会疑惑为什么只能选择一张表?如果SQL中有2个或多个FACT表,是否可以并行扫描所有FACT表?答案是当然可以。以下SQL为例:SELECTo.o_custkey,sum(l.l_extendedprice)assFROMorderso,lineitemlWHEREo.o_custkey=l.l_orderkeyGROUPBYo.o_custkeyORDERBYsLIMIT10;其中orders表和lineitem表是数据量较大的事实表,这条SQL的并行执行计划如下图所示:从计划中我们可以看到orders表和lineitem表都会被扫描并行,两者都由32个工作线程执行。那么多表的并行是如何实现的呢?我们以两个表为例。两个表进行JOIN时,常见的JOIN方式有NestedLoopJOIN、HASHJOIN等,针对不同的JOIN方式,为了保证结果的正确性,必须选择合理的表扫描方式。以HASHJOIN为例。对于串行执行的HASHJOIN,首先选择一个表创建一个名为Buildtable的HASH表,然后读取另一个Probe表,计算HASH,在Build表中进行HASH匹配。如果匹配成功,则输出结果,否则继续阅读。如果改为并行HASHJOIN,并行优化器会对串行执行的HASHJOIN进行并行转换,使其成为并行HASHJOIN。并行转换有两种解决方案。方案一是将两张表按照HASH键进行分区,HASH值相同的数据在同一个分区,同一个线程执行HASHJOIN。方案2是创建一个共享的Build表,所有执行HASHJOIN的线程共享,然后每个线程并行读取属于自己线程的另一个表的分片,然后执行HASHJOIN。最终选择哪种方案由成本估算决定。图2ParallelHASHJOIN示意图对于方案1,需要读取表中所有数据,根据选择的HASHkey对数据进行分区,并将数据发送到不同的处理线程,这就需要额外增加一个Repartition算子,即负责将数据按照分区规则发送到不同的处理线程。对于方案二,需要并行创建一个共享的HASH建表。建表成功后,各线程读取Probe表的一个片段,分别执行HASHJOIN。这里的分片不需要按照HASHkey分片,每个线程可以单独读取不相交的分片。5、分析统计复杂算子的并行操作对于一个分析统计需求,GROUPBY操作是不可避免的操作,尤其是对大量JOIN结果的GROUPBY操作是整个SQL中最耗时的过程,所以GROUPBY的并行性也是并行查询引擎首先要解决的问题。以年消费前10大客户的SQL为例,将GROUPBY并行化后的并行执行计划如下图所示:新的执行计划与之前的执行计划相比,多了一个收集器组件,总共有2个收集器组件。首先,让我们看一下第二行的收集器组件。它的extrainformation里面有2条“Usingtemporary;Usingfilesort”,意思是它对从worker收到的数据进行GROUPBY,然后按ORDER排序,因为用户的session中只有第一个A收集器组件,所以这个collector在worker中也是并行执行的,也就是说Groupby、Orderby、Limit是并行执行的;然后看第一行的collector组件,里面只有一个额外的信息“Mergesort”,意思是session线程对从worker接收到的数据进行归并排序,然后将结果返回给用户.这里可能有人会问,为什么session线程只做mergesort就可以完成GROUPBY操作呢?另外,LIMIT在哪里?先回答第二个问题,因为explainplan显示的问题,在normal模式下没有显示LIMIT操作,在Tree模式下显示LIMIT操作。如下图:从Tree-typeplantree可以清楚的看到有两个LIMIT操作,一个是在plan的最顶端,也就是session上,执行完之后返回给用户的数据限额完成;另一个在计划树的中间位置,其实是在工作线程的执行计划上。在每个worker线程中,排序完成后会做一个limit,这样可以大大减少worker返回给session线程的数据量,从而提高整体性能。.先回答第一个问题,为什么GROUPBY只需要在worker线程执行一次就可以保证结果的正确性。一般来说,每个worker只有一个shard所有的数据,只在一个datashard上做GROUPBY得到错误GROUPBY结果的风险很大,因为同一个GROUP组内的数据可能不仅在这个WORKER中,也可能也被其他WORKER持有在其他WORKER的数据分片中。但是如果我们能够保证同一个GROUP分组的数据一定在同一个数据分片中,并且这个数据分片只被一个WORKER线程持有,那么GROUPBY结果的正确性就可以得到保证。从Tree-type执行计划可以看出,并行JOIN后,JOIN的结果根据GROUP:c.c_name的KEY值进行分组进行Repartition操作,将同组的数据分发到同一个WORKER,从而保证每个WORKER的数据分片不会相互交叉,保证GROUPBY结果的正确性。因为每个WORKER的GROUPBY操作都是最终结果,所以ORDERBY和LIMIT也可以下推给WORKER执行,进一步提高了并行执行的效率。6.并行查询引擎对TPCH的线性加速附图为并行查询引擎对TPCH的加速效果。TPC-H中100%的SQL可以加速,70%的SQL可以加速8倍以上,总加速近13倍,Q6和Q12加速甚至超过32倍。7.小结数据库是应用系统的核心,优化器是数据库的核心。优化器的好坏几乎可以决定一个数据库产品的成败。开发一个全新的优化器对任何团队来说都是一个巨大的挑战。撇开技术的复杂性不谈,要让产品足够稳定是一个非常困难的难点。因此,即使是传统的商业数据库,也在现有优化器的基础上不断改进,逐步增加对并行性的支持,最终成为成熟的并行优化器。PolarDB也是如此。在设计开发并行查询引擎时,我们充分利用现有优化器的技术积累和实现基础,不断完善和打磨,最终形成不断迭代的技术方案,确保新优化器的稳定运行和技术创新。
