Oracle并行基础(连载二)

作者:沃趣科技高级数据库技术专家 魏兴华



消费者生产者模型的限制

根据上面的介绍,你已经知道了,一个并行操作内一般会具有两组PX slave进程,一组为生产者,一组为消费者。生产者通过table queue发送数据,消费者通过table queue接收数据。而且对于消费者和生产者模型,有一个很大的限制是:一组DFO单元最多只能有两组PX slave进程,之所以有这个限制,一方面可能是Oracle公司为了保持并行代码的简洁性,一方面由于每个PX slave进程之间和每个PX slave与QC之间都要维持一个通信通道(table queue)用于传递消息和数据,如果允许的PX slave有太多组,可能会导致通信通道指数级增长。例如一个DOP为5的并行操作,PX slave之间需要的通道数为55,PX slave与QC之间的通道数为25,共需要(5+2)5=35个通道。可想而知,如果Oracle允许一个并行操作内有3组PX slave,需要维持的连接数有多少,我们假设当前服务器共运行了50个并行,那么三组PX slave进程产生的通道数为5050*50=125000个,还不包括PX slave与QC之间的通道,吓尿了不?

parallel_execution_message_size

如果进程之间传递消息的通道数多但不占用数据库资源可能也并不是什么大的问题,但是事实不是这样的,进程之间传递消息的通道的内存占用大小是由参数parallel_execution_message_size控制的,在11GR2版本这个参数的值为16K,在以前的各个版本这个参数的值可能大小并不一样(每个版本都有增大的趋势),在非RAC环境下,每个通道的大小最大可以为3parallel_execution_message_size,在RAC环境下,每个通道的大小最大可以为4parallel_execution_message_size。
例如一个DOP为20的查询,非rac环境下通道所占用的内存最大可能为:
PX进程的通道内存+QC、PX进程之间的通道内存=202016K3+22016K3=21120K,接近21M的内存。
通道的内存默认是在large pool中分配,如果没有配置large pool则在shared pool中分配。
计算通道内存的公式:
单节点
(NN+2N)316k
RAC节点
(NN+2N)416k

hash join buffered

其实不是hash join的缺陷。
我们已经介绍过生产者消费者模型,它有一个很大“缺陷”是,一个并行操作内,最多只能有2组PX slave,2组PX slave通过table queue来传递消息和交互数据,因此在一组SLAVE在读table queue的时候,不能同时去写另一个table queue。是不是不太好理解?
我们通过一个例子来进行描述:

select /*+ parallel(6) pq_distribute(b hash hash)*/ * from hash_t3 a ,hash_t1 b where a.id=b.id;


  这个例子里我通过hint pq_distribute(b hash hash)强行让数据以hash方式进行分发,关注行源ID为3的操作,出现很陌生的一个操作:hash join buffered,不要被这个buffered名字所迷惑,它代表着数据暂时不能向上流动,必须先暂时存放在这里,语句的执行顺序是这样的:

  • 首先红色的生产者PX slave扫描hash_t3表,并对扫描的记录按照HASH分发方式把相关记录写入table queue TQ10000

  • 蓝色的消费者PX slave从table queue TQ10000接收数据并构建hash table。

  • 上面操作结束后,红色的生产者继续扫描hash_t1表,并对扫描的记录按照HASH分发方式写入table queue TQ10001

  • 蓝色的消费者PX slave从table queue TQ10001接收数据,并与上面的HASH TABLE做探测,但是结果并不能写入table queue TQ10002,而是先暂时缓存起来(hash join buffered的由来)

  • 等HASH分发完成之后(也就是这两组PX slave不活跃以后),然后由一组PX slave把结果集通过table queue TQ10002发送给QC。

为什么要这样?貌似是没有道理的。
这就是因为hash分发要求对hash join的右边也要进行分发,分发操作涉及了2组PX slave进程,一组负责扫描,一组负责接收数据,也就是一组PX slave把扫描的数据写入table queue,一组负责从table queue读取数据,这个时候不能 再进行数据的分发操作,因为join的结果集不能写入另一个table queue TQ10002。
如果结果集较大的话,这个可能在一定程度上会导致消耗很多临时表空间,导致大量的磁盘读写IO,进而引起性能降低。
如果确实产生了这种情况,可以通过改用broadcast分发来避免出现这种情况,因为broadcast分发对于hash join的右边并不需要进行分发

select /*+ parallel(6) pq_distribute(b broadcast none)*/ * from hash_t3 a ,hash_t1 b where a.id=b.id;


例如改成broadcast后,hash join buffered操作已经消失了。

布隆过滤

我们有必要介绍一下布隆过滤,它在11GR2之后版本的并行里有非常大的作用。bloom filter并非Oracle的发明,bloom filter技术出现的时候Oracle软件还未诞生,它在1970年由Burton H.Bloom开发出来,布隆过滤到什么?
布隆过滤或者说布隆过滤器,是一种数据结构,它能够快速的判断一个数据是否属于一个集合,hash join本身是非常消耗资源的,也是非常慢的,布隆过滤比hash join快很多。

关于布隆过滤的详细介绍请参照:http://www.cnblogs.com/haippy/archive/2012/07/13/2590351.html


布隆过滤器基于一个有M位的数组,例如上图数组的大小有18位,初始化的时候全部的值都为0,如果要理解布隆过滤是如何工作的,必须要知道在什么情况下,这些标志位需要置为1,上图中{X,Y,Z}代表着一个集合,这个集合有3个值(元素),仔细观察每一个值都延伸出了三个线,在这里代表着每一个值都经过3个HASH函数计算,计算出来的值的范围是从0-17(数组的长度),例如,X经过3次HASH函数计算,值分别为:1,3,13,然后对应的标志位被置为1,Y,Z同理把相应的标志位置为1。经过一番HASH计算,{X,Y,Z}集合的所有元素都已经经过了HASH计算,对应的标志位也都置为了1,然后我们再探测另一个集合,这里另一个集合的元素为W,W同样需要经过相同的3个HASH函数计算,并且检测对应的位置是否为1,如果对应的位置都为一,那么W可能(仅仅是可能)属于这个集合,如果有任何的位置不为1,那么这个W一定不属于这个集合。由于布隆过滤并不对值进行精确的匹配(而HASH JOIN是需要精确匹配的),因此可能会有一些不该属于集合的值穿越了布隆过滤器。

布隆过滤器有如下特点:

  • 构建布隆过滤数组要求的内存非常小,经常可以完全放入在CPU的cache中。当然布隆过滤的数组越大,布隆过滤误判的可能性也就越小。

  • 由于不需要精确匹配,因此布隆过滤的速度非常的快,但是有一些不该出现的值可能会穿越布隆过滤器。


PX Deq Credit: send blkd 与PX Deq Credit: need buffer

布隆过滤有啥用呢?也许你是一位有着丰富经验的老DBA,那么你对PX Deq Credit: send blkd 、PX Deq Credit: need buffer等待事件也许就比较熟悉。经过上面的介绍,我们已经具备了很多的知识,table queue,生产者消费者模型等等,一组消费者PX slave写入table queue,另一组通过读取table queue来获取数据,完成进程间数据的传递,但是一定会出现一种情况,当一组生产者PX slave在往table queue中写入数据的时候,发现table que中的内存已经满了,没有剩余内存可以写了,这种情况大部分时候都意味着消费者PX slave从table queue中消费数据过慢,过慢最大可能原因是由于消费者不得不把table queue中读取到的数据溢出到磁盘,从内存读取数据写入磁盘是个很慢的操作,因此在这种情况下,就会遭遇PX Deq Credit: send blkd 、PX Deq Credit: need buffer等待,如何优化?这种情况下,布隆过滤就发挥了作用。
如果优化器认为表X返回1000条记录,表Y需要扫描一亿条记录,但是经过HASH JOIN后,有90%都不需要返回,这种情况下使用布隆过滤在进行HASH分发前预HASH JON。这样经过布隆过滤器,有大量的记录就被布隆过滤器所淘汰,最后HASH JOIN右边的结果集就变得非常小,也就让HASH 分发的数据量变得非常的小,大大减少了出现PX Deq Credit: send blkd 、PX Deq Credit: need buffer的概率。如果不使用布隆过滤,进程不得不传递大量的数据给另一组进程,增加了内存,CPU,增加了两组进程的进程间竞争。
不要期待布隆过滤是完美的,他能消除掉大部分的行,但是不是 所有的行,因此有一些不需要的数据会穿过布隆过滤器达到第二组进程。

并行度降级

无论你使用的是手工指定DOP,还是使用11G的AUTO DOP,运行时的DOP都有可能与你预期的不一样:可能被降级。可能会有很多种原因导致并行被降低,例如,当前系统中可用的并行进程已经不能满足需要的DOP,或者你已经使用了Oracle的资源管理器对并行度做了限制,等等。
监控并行度降低的最好工具是oracle 12.1版本的SQL MONITORING,例如:


如上图,在【一般信息】部分,将你的鼠标放在Execution Plan部分的蓝色小人上,将会出现一些并行度的信息,例如上图中,运行时间的DOP为4,实际请求的并行服务进程为10,实际分配的并行服务进程为4,并行度被降低的百分比为60%。
为了找出语句被降级的理由,你可以点击【计划统计信息】部分,PX COORDINATOR行源的其他列,如下图,用红色框标记:
点击后出现:

以下是被降级的一些代码说明:
350 DOP downgrade due to adaptive DOP
351 DOP downgrade due to resource manager max DOP
352 DOP downgrade due to insufficient number of processes
353 DOP downgrade because slaves failed to join
我这里的情况是,由于系统可以使用的并行进程不足导致分配并行资源失败。
如果你不方便使用EMCC,也可以通过视图观察到并行度降级的情况,但是被降级的理由,暂时还没有视图反应(或者我还不知道,如果你知道请告诉我)

DEGREE 列为实际的并行度,REQ_DEGREE 为请求的并行度。
有一些手段可以避免并行度降级,例如如果使用的是ORACLE 11G版本,可以使用自动并行管理功能,然后结合在语句级指定并行度。因为自动并行度功能一单被打开,并行语句排队功能将被启用,如果语句运行时发现没有足够的可用并行进程,那么会排队等待,直到有满足目标的可用并行进程。

多个DFO 单元

一些命令可以有多个DFO单元,因为每个DFO单元最多可以使用2个PX slaves set,如果一个命令有多个DFO单元,那么它就可以使用超过2个PX slaves set,可以在执行计划里看到是否使用了多个DFO单元:
select count() from (select /+ parallel(a 4) /count() from hash_t1 a
union
select /+ parallel(b 4) / count(*) from hash_t2 b)


行ID为6和12的行源两处都有coordinator标识,这意味着这个命令使用了2个DFO单元。

通过SQL MONITORING也可以看到这个命令具有了2个并行组,理论上每个DFO单元之间可以同时进行并行操作,但是我们这个例子里,两个DFO单元之间的执行顺序是,先执行DFO单元1,再执行DFO单元2,可以通过【时间表】列看到,第一个DFO单元先活跃,等结束后,第二个DFO单元开始活跃。
从上图还可以看出,DFO单元2复用了DFO单元1的并行进程,没有重新产生新的并行进程,从并行进程编号上可以看出这一点。SQL MONTIRONG是不是超级好用?

v$pq_sesstat视图


通过查询v$pq_sesstat视图,可以知道语句运行时的DFO单元的数量(DFO Trees),并行集的个数(Slave Sets ),服务进程的个数(Server Threads),执行所采用的并行度(DOP)。如下:

写到这里文章已经有点长了,对于12C的新特性还少有涉及,对于并行执行倾斜的内容也还未涉及,对于布隆过滤的传递和高级知识也未涉及。对于这些内容,我会在下一篇进行介绍。