SQL性能优化有没有什么灵丹妙药? 

看见这个问题的你是不是默默的笑了,肯定有吧~~ 然后不同的你脑海里马上浮现出了不同的灵丹妙药,呵呵~~

好了,现在有个SQL,1.2分钟执行完,目标是20秒内完成。这个SQL的SQL Monitor Report的获取方法在下方原文链接,我想请你现在就去拿到SQL Monitor Report,然后评估一下,用你脑海里刚刚浮现出的灵丹妙药,能不能完成目标?

评估完之后,来看一下我们的观察结果。

SQL性能优化实例解析(2)_Java

不知道有没有人的灵丹妙药是,用并行?看看直接使用了并行的结果吧。

SQL性能优化实例解析(2)_Java_02

并行度是4,执行时间37秒,失不失望?没事儿,谁听见你说要用并行了,刚才你啥都没说。

你细看,这个并行的执行计划,和原始的串行的执行计划,本质上是一样的,还是两个表Nested Loops Join,只是多了并行执行的那些operator(PX开头的那些operator)。并行,本质上是资源管理的方法,用更多的资源去做一个事情,而不是SQL优化的方法。而且并行也有overhead,这里的并行度我们用的是4,可是执行时间从1.2分钟变成37秒,快了2倍,可不是快了4倍。

你要是简单的想,并行度4快2倍,那加大并行度,使用并行度16,32,128,是不是就肯定能达到目标了,那我就必须要提醒你,关于并行度,自己好好去做些实验,如果看到执行某些SQL时,并行度32比并行度4还慢的情况,不要惊讶,想想为什么。不理解并行的overhead,可能会吃大亏。

好了,来看这条SQL的分析优化。读了我们上一期文章的话(SQL性能优化实例解析(1)),肯定马上就知道入手点在哪里。对哒,最右边,Activity%那一列,表示的是执行每一步的时候,对应的数据库里面的活动(Activity)的样本数,你看到的颜色条最长的步骤,意味着执行那一步花的时间最长,就是入手点。

接下来,要检查入手点的具体情况。入手点是访问LINEORDER表,方法是TABLE ACCESS BY GLOBAL INDEX ROWID,IO Bytes是9GB,Estimated Rows(估计行数)是2983,Actual Rows(实际行数)是2124K。

估计行数和实际行数相差了700多倍,你有没有一种冲动,快点去收集一下LINDORDER表的统计信息?

且慢,上一个层次看问题,总是特别的重要。

入手点是访问LINEORDER表,这个步骤是在一个Nested Loops Join里面进行的。上一个层次,Join的方法好不好?如果Join方法不好,那就是大方向搞错了,还纠结细节有啥用呢。

看Join的方法好不好,要看Join的驱动表,在我们这个例子里面,驱动表是SUPPLIER表,Estimated Rows(估计行数)是1,Actual Rows(实际行数)是708,差了708倍。

Nested Loops Join的执行方法是,对于驱动表在应用一般过滤条件后返回的每一行,去Join的另外一个表里根据Join Key在全表范围内查找匹配的数据。就是说,如果驱动表返回的数据是1行,需要去另一个表里全表查找1遍,如果驱动表返回的数据是708行,需要去另一个表里全表查找708遍。

LINEORDER表是个事实表,是个大表,对LINEORDER表全表范围查询1遍还是708遍,可是个巨大的性能差异,不管用不用索引去访问LINEORDER表。

所以问题是,优化器对SUPPLIER表的估计为什么会差了700多倍?读了我们上一期文章的话,肯定马上会想到,检查一下SQL Monitor Report的Plan tab,看看SUPPLIER表上的predicate是什么。

SQL性能优化实例解析(2)_Java_03

SUPPLIER表上只有一个简单的过滤条件。肯定不存在我们上一篇例子里面的列相关的情况。

优化器进行估计的时候,统计信息是重要输入,所以我们肯定要去检查SUPPLIER表的统计信息。检查结果发现SUPPLIER表的统计信息陈旧,NUM_ROWS是10,我们知道这肯定是错的,因为SQL Monitor Report里面访问SUPPLIER表的Actual Rows(实际行数)都是708了,SUPPLIER表怎么可能只有10行数据。

对SUPPLIER表重新收集统计信息,来看一下效果

SQL性能优化实例解析(2)_Java_04


在SUPPLIER表更新了统计信息之后,优化器对于访问SUPPLIER表返回的数据量做出了非常准确的估计,Estimated Rows(估计行数)就是708,因此,选择了HASH JOIN的方式,并且还自动使用了Bloom Filter(JOIN FILTER CREATE和JOIN FILTER USE那两个operator)来进一步优化HASH JOIN。

SQL的执行时间现在是6秒钟,目标完成。

目标完成了?嗯,美好降临的太突然了有木有?回味一下重点:

如果SQL Monitor Report里面花时间最多的步骤,是在Nested Loops Join里面,那么首先要检查Nested Loops Join的驱动表,对比驱动表的Estimated Rows(估计行数)和Actual Rows(实际行数)。如果有数量级的差异,那么检查驱动表的统计信息并修正。

意识到上一个层次的重要性了吗?达成目标了,对,还有呢?

快速达成了目标,有木有?SUPPLIER表是个小表,收集统计信息很快完成啦,所以上一个层次,找到问题的根本所在,很快就达成目标了。

否则,如果你上来一看见访问LINEORDER表的那条Activity%最长,就不管不顾的去收集LINEORDER表的统计信息,收集完了不管用不说,你可是要花很长时间才能知道不管用,因为LINEORDER表是个大表啊,收集统计信息花的时间,肯定比收集SUPPLIER表的统计信息长的多啊。

你想想,你有没有总是很忙?有没有经常忙着去干类似于收集LINEORDER表的统计信息这样的事?试试上一个层次思考问题,生活可以更美好哒~~

SQL性能优化实例解析(2)_Java_05

总结一下

问题SQL,执行时间1.2分钟

  •  拿到问题SQL的SQL Monitor Report (数据库自带

  •  直接尝试使用并行度4去执行,执行时间37秒,不能达到目标

  • 检查最右边的Activity%列,找出颜色条最长的那一行,做为入手点

  • 如果入手点那一行是在Nested Loops Join里面,那么首先要检查Nested Loops Join的驱动表(在我们的例子里是SUPPLIER表),对比驱动表的Estimated Rows(估计行数)和Actual Rows(实际行数)。如果有数量级的差异,那么检查驱动表的统计信息并修正。我们这个例子里,SUPPLIER表估计行数是1,实际行数是708

  • SQL Monitor Report里面有好几个tab,信息很全,可以通过这些信息帮助进一步定位问题。我们这个例子里面使用了Plan tab里的Predicate信息来帮助进一步确认问题

  • 我们定位出问题根源是Nested Loops Join的驱动表SUPPLIER表的统计信息陈旧

  • 针对问题根源,实施收集SUPPLIER表统计信息这个解决方案

  • 依然使用并行度4去执行,SQL执行时间变为6秒,目标达成

上一个层次很重要。本文的例子里,入手点的上一个层次是Join方法。站在上一个层次看问题,就是要去收集SUPPLIER表的统计信息,而不是LINEORDER表的统计信息。就这样把SQL优化变成了哲学问题,好玩儿吗?你深刻的理解了吗?

等着,过几天弄个复杂但是同理的来考考你,看你能不能熟练应用这个哲学原理~~

更多SQL性能优化实例解析,欢迎在哔哩哔哩(app或微信小程序)上搜索“Oracle公益课堂”,就可以看到视频啦 -“RWP大开眼界系列-SQL Monitor Report分析”