对自定义函数开启parallel_enable属性使函数可以并行来提升SQL查询性能

Posted robinson1988

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了对自定义函数开启parallel_enable属性使函数可以并行来提升SQL查询性能相关的知识,希望对你有一定的参考价值。

***人社系统最近做了数据迁移,采用国产的Zdata一体机替换了老旧的小型机,数据库也从11g升级为了19c
之前整个系统被拆分为5套子系统,这次升级将5套子系统做了整合,采用pdb的方式将5套子系统统一存放在Zdata一体机中
完成数据迁移之后,在没有针对SQL进行专门的优化的前提下,从各项指标上看,系统的整体性能提升了15-20倍
虽然整个系统的性能有了巨大提升,但是我们觉得还不够,我们的目标是将性能提升100倍
前几天对系统中一个老大难的SQL做了等价改写,SQL从每次执行5到10分钟降低为了15秒
今天继续优化跑得比较慢的存储过程,下面是从存储过程中抓出的缓慢SQL,每次执行要跑738-800秒

SELECT round(sum(decode(ac83.aaa036,
                        '100012',
                        pkg_B_Comm.Fun_GetAae019ByAaa088(ac83.aaa088,
                                                         ac83.aae019))) /
             10000,
             2),
       round(sum(decode(ac83.aaa036,
                        '100500',
                        pkg_B_Comm.Fun_GetAae019ByAaa088(ac83.aaa088,
                                                         ac83.aae019))) /
             10000,
             2)
  FROM ic10, ab01, ac01, ac82, ac83
 WHERE ic10.aab001 = ab01.aab001
   AND ic10.aac001 = ac01.aac001
   AND ic10.aac001 = ac82.aac001
   AND ic10.aae140 = ac82.aae140
   AND ic10.aaa027 = ac82.aaa027
   AND ac82.aaz220 = ac83.aaz220
   AND AC82.AAE117 <> '4'
   AND AC83.AAA036 IN ('100012', '100500')
   AND ic10.aae140 = '110'
   AND ac82.aae002 >= 202001
   AND ac82.aae002 <= 202012
   AND ac83.aae003 >= 202001
   AND ac83.aae003 <= 202012
   AND ac83.aae001 = 2020
   AND ic10.aaa027 = '519900'
   AND NVL(AB01.YAB019, '01') in ('01')
   AND NVL(AB01.AAB019, '99') IN ('10')
   AND NVL(AB01.AAB020, '900') IN ('110');

执行计划如下:  

SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 3793786172
------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                 | Name                     | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     | Pstart| Pstop |
------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT          |                          |     1 |   162 |       |  1956K  (1)| 00:01:17 |       |       |
|   1 |  SORT AGGREGATE           |                          |     1 |   162 |       |            |          |       |       |
|*  2 |   HASH JOIN               |                          |  1280K|   197M|    90M|  1956K  (1)| 00:01:17 |       |       |
|*  3 |    HASH JOIN              |                          |   739K|    81M|    21M|   374K  (1)| 00:00:15 |       |       |
|*  4 |     HASH JOIN             |                          |   286K|    18M|    18M| 82831   (1)| 00:00:04 |       |       |
|*  5 |      HASH JOIN            |                          |   286K|    15M|       | 31824   (1)| 00:00:02 |       |       |
|*  6 |       MAT_VIEW ACCESS FULL| AB01                     | 15060 |   308K|       |  8313   (1)| 00:00:01 |       |       |
|*  7 |       TABLE ACCESS FULL   | IC10                     |   527K|    17M|       | 23509   (1)| 00:00:01 |       |       |
|   8 |      INDEX FAST FULL SCAN | SYS_C_SNAP$_26083PK_AC01 |    26M|   276M|       | 21309   (1)| 00:00:01 |       |       |
|   9 |     PARTITION RANGE SINGLE|                          |  5067K|   241M|       |   275K  (1)| 00:00:11 |    12 |    12 |
|* 10 |      TABLE ACCESS FULL    | AC82                     |  5067K|   241M|       |   275K  (1)| 00:00:11 |    12 |    12 |
|  11 |    PARTITION RANGE SINGLE |                          |    38M|  1672M|       |  1473K  (1)| 00:00:58 |    12 |    12 |
|* 12 |     TABLE ACCESS FULL     | AC83                     |    38M|  1672M|       |  1473K  (1)| 00:00:58 |    12 |    12 |
------------------------------------------------------------------------------------------------------------------------------
PLAN_TABLE_OUTPUT
---------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("AC82"."AAZ220"="AC83"."AAZ220")
   3 - access("IC10"."AAC001"="AC82"."AAC001" AND "IC10"."AAE140"="AC82"."AAE140" AND "IC10"."AAA027"="AC82"."AAA027")
   4 - access("IC10"."AAC001"="AC01"."AAC001")
   5 - access("IC10"."AAB001"="AB01"."AAB001")
   6 - filter(NVL("AB01"."AAB020",'900')='110' AND NVL("AB01"."AAB019",'99')='10' AND NVL("AB01"."YAB019",'01')='01')
   7 - filter("IC10"."AAA027"='519900' AND "IC10"."AAE140"='110')
  10 - filter("AC82"."AAA027"='519900' AND "AC82"."AAE117"<>'4' AND "AC82"."AAE140"='110' AND "AC82"."AAE002"<=202012)
  12 - filter(("AC83"."AAA036"='100012' OR "AC83"."AAA036"='100500') AND "AC83"."AAE003">=202001 AND
              "AC83"."AAE003"<=202012 AND "AC83"."AAE001"=2020)
Note
-----
   - this is an adaptive plan

PARTITION RANGE SINGLE 表示做了分区裁剪,MAT_VIEW ACCESS FULL 表示走了物化视图全表扫描,这个执行计划没有问题
该存储过程是一个OLAP业务,对于OLAP的优化,通常采用 分区+并行+表垂直拆分 等手段进行优化
因为做了分区裁剪,也走了物化视图,所以分区这一招 我们就不用做了(已经做了) 
现在开启并行看一下SQL查询速度

SELECT /*+ parallel(4) */ round(sum(decode(ac83.aaa036,
                        '100012',
                        pkg_B_Comm.Fun_GetAae019ByAaa088(ac83.aaa088,
                                                         ac83.aae019))) /
             10000,
             2),
       round(sum(decode(ac83.aaa036,
                        '100500',
                        pkg_B_Comm.Fun_GetAae019ByAaa088(ac83.aaa088,
                                                         ac83.aae019))) /
             10000,
             2)
  FROM ic10, ab01, ac01, ac82, ac83
 WHERE ic10.aab001 = ab01.aab001
   AND ic10.aac001 = ac01.aac001
   AND ic10.aac001 = ac82.aac001
   AND ic10.aae140 = ac82.aae140
   AND ic10.aaa027 = ac82.aaa027
   AND ac82.aaz220 = ac83.aaz220
   AND AC82.AAE117 <> '4'
   AND AC83.AAA036 IN ('100012', '100500')
   AND ic10.aae140 = '110'
   AND ac82.aae002 >= 202001
   AND ac82.aae002 <= 202012
   AND ac83.aae003 >= 202001
   AND ac83.aae003 <= 202012
   AND ac83.aae001 = 2020
   AND ic10.aaa027 = '519900'
   AND NVL(AB01.YAB019, '01') in ('01')
   AND NVL(AB01.AAB019, '99') IN ('10')
   AND NVL(AB01.AAB020, '900') IN ('110');

发现跑了1分钟没出结果,赶紧停掉,瞄了一眼代码,这个代码有自定义函数pkg_B_Comm.Fun_GetAae019ByAaa088
将自定义函数部分去掉,开并行查看速度 --- 17秒

pkg_B_Comm.Fun_GetAae019ByAaa088的代码如下:

function SCSB01.Fun_GetAae019ByAaa088(prm_aaa088 in varchar2, --应付类型
                                      prm_aae019 in number --原金额
                                      )
  return number is
  num_aae019 number(14, 2);
begin
  if prm_aaa088 in ('03', '06', '09', '11') then
    --特殊扣发、调整扣发、退发收款、正常扣发
    num_aae019 := -nvl(prm_aae019, 0);
  else
    num_aae019 := nvl(prm_aae019, 0);
  end if;
  return num_aae019;
end Fun_GetAae019ByAaa088;

这个自定义函数挺简单的,就一个if判断,没有对其他表进行查询
像这种简单的自定义函数,一般是不太会影响SQL性能的(有,SQL引擎与PLSQL引擎上下文切换,但是影响不太大)

现在定位到是因为自定义函数导致开并行也跑得慢,有两个解决办法:

1. 将函数代码改写到SQL中,不去调用函数
2. 创建函数的时候,启用parallel_enable,让函数也可以开并行

我这里选择的是第二个方案,创建了一个测试函数,对函数开启parallel_enable

create or replace function SCSB01.test_fun_getaae019byaaa088(prm_aaa088 in varchar2, --应付类型
                                                             prm_aae019 in number --原金额
                                                             )
  return number parallel_enable is
  num_aae019 number(14, 2);
begin
  if prm_aaa088 in ('03', '06', '09', '11') then
    --特殊扣发、调整扣发、退发收款、正常扣发
    num_aae019 := -nvl(prm_aae019, 0);
  else
    num_aae019 := nvl(prm_aae019, 0);
  end if;
  return num_aae019;
end;

最终SQL可以20秒跑完

SELECT /*+ parallel(4) */ round(sum(decode(ac83.aaa036,
                        '100012',
                        test_fun_getaae019byaaa088(ac83.aaa088,
                                                         ac83.aae019))) /
             10000,
             2),
       round(sum(decode(ac83.aaa036,
                        '100500',
                        test_fun_getaae019byaaa088(ac83.aaa088,
                                                         ac83.aae019))) /
             10000,
             2)
  FROM ic10, ab01, ac01, ac82, ac83
 WHERE ic10.aab001 = ab01.aab001
   AND ic10.aac001 = ac01.aac001
   AND ic10.aac001 = ac82.aac001
   AND ic10.aae140 = ac82.aae140
   AND ic10.aaa027 = ac82.aaa027
   AND ac82.aaz220 = ac83.aaz220
   AND AC82.AAE117 <> '4'
   AND AC83.AAA036 IN ('100012', '100500')
   AND ic10.aae140 = '110'
   AND ac82.aae002 >= 202001
   AND ac82.aae002 <= 202012
   AND ac83.aae003 >= 202001
   AND ac83.aae003 <= 202012
   AND ac83.aae001 = 2020
   AND ic10.aaa027 = '519900'
   AND NVL(AB01.YAB019, '01') in ('01')
   AND NVL(AB01.AAB019, '99') IN ('10')
   AND NVL(AB01.AAB020, '900') IN ('110');

如果还想对SQL进行优化,其实也可以,注意观察SQL语句的SELECT列,只访问极少列
可以对表添加组合索引走IFFS,或者对表做垂直拆分,但是现在已经20秒了,那就这样吧
 

以上是关于对自定义函数开启parallel_enable属性使函数可以并行来提升SQL查询性能的主要内容,如果未能解决你的问题,请参考以下文章

对自定义属性执行客户端验证

按属性对自定义对象的 ArrayList 进行排序

在Objective-C中按一个属性对自定义对象的NSSet进行排序

Java:通用的排序工具类,能够对自定义对象,针对不同的属性(字段),实现排序(正序倒序)

对自定义类列表进行排序<T>

在python中对自定义类执行集合操作