对自定义函数使用不当的调优案例

一、问题。

    下面这条SQL的运行时间较长,不满足业务需求。

    select rs.managecom,

       (select codename

          from ldcode

         where codetype = 'station'

           and code = rs.managecom),

       rs.insurername,

       rs.contplancode,

       case

         when sum(prem) - sum(prem2) + sum(prem3) = 0 then

          ''

         else

          case

            when substr(round((sum(rs.pay) + sum(rs.pay2)) /

                              (sum(prem) - sum(prem2) + sum(prem3)),

                              4) * 100,

                        0,

                        1) = '.' then

             '0' || round((sum(rs.pay) + sum(rs.pay2)) /

                          (sum(prem) - sum(prem2) + sum(prem3)),

                          4) * 100 || '%'

            else

             round((sum(rs.pay) + sum(rs.pay2)) /

                   (sum(prem) - sum(prem2) + sum(prem3)),

                   4) * 100 || '%'

          end

       end,

       sum(rs.pay),

       sum(rs.pay2),

       round(sum(prem), 2) - round(sum(prem2), 2) + round(sum(prem3), 2),

       round(sum(prem), 2),

       round(sum(prem2), 2),

       round(sum(prem3), 2)

  from (select g.managecom,

               g.insurername,

               (SELECT LN.CONTPLANNAME

                  FROM LCContPlan LN

                 WHERE LN.CONTPLANCODE = d.CONTPLANCODE

                   AND LN.GrpContno = d.GrpContno

                   AND ROWNUM = 1) contplancode,

               d.insuredno,

               nvl((select sum(a.realpay)

                     from llclaimdetail a, llclaim b

                    where a.clmno = b.clmno

                      and a.grpcontno = c.grpcontno

                      and a.customerno = d.insuredno

                      and a.givetype != '1'

                      and b.clmstate = '60'

                      and b.endcasedate >= date

                    '2015-01-01'

                      and b.endcasedate <= date '2015-11-30'),

                   0) pay,

               nvl((select sum(d.realpay)

                     from LLClaimPolicyBak d, llclaim b

                    where d.grpcontno = c.grpcontno

                      and d.clmno = b.clmno

                      and d.insuredno = c.insuredno

                      and (d.clmstate not in ('60', '70', '100') or

                          d.clmstate is null)

                      and (b.endcasedate is null or b.endcasedate > date

                           '2015-11-30')

                      and d.makedate <= date

                    '2015-11-30'

                      and d.seqno =

                          (select max(seqno)

                             from LLClaimPolicyBak dd, llclaim bb

                            where dd.grpcontno = c.grpcontno

                              and dd.clmno = d.clmno

                              and dd.clmno = bb.clmno

                              and d.riskcode = dd.riskcode

                              and not exists

                            (select 'X'

                                     from llclaimdetailb

                                    where llclaimdetailb.clmno = dd.clmno

                                      and llclaimdetailb.riskcode =

                                          dd.riskcode

                                      and llclaimdetailb.makedate =

                                          dd.makedate)

                              and dd.makedate <= date

                            '2015-11-30'

                              and (dd.clmstate not in ('60', '70', '100') or

                                  dd.clmstate is null)

                              and (bb.endcasedate is null or

                                  bb.endcasedate > date '2015-11-30'))),

                   0) -

               nvl((select sum(d.realpay)

                     from LLClaimPolicyBak d, llclaim b

                    where d.grpcontno = c.grpcontno

                      and d.clmno = b.clmno

                      and d.insuredno = c.insuredno

                      and (d.clmstate not in ('60', '70', '100') or

                          d.clmstate is null)

                      and (b.endcasedate is null or b.endcasedate > date

                           '2015-01-01' - 1)

                      and d.makedate <= date

                    '2015-01-01' - 1

                      and d.seqno =

                          (select max(seqno)

                             from LLClaimPolicyBak dd, llclaim bb

                            where dd.grpcontno = c.grpcontno

                              and dd.clmno = d.clmno

                              and dd.clmno = bb.clmno

                              and not exists

                            (select 'X'

                                     from llclaimdetailb

                                    where llclaimdetailb.clmno = dd.clmno

                                      and llclaimdetailb.riskcode =

                                          dd.riskcode

                                      and llclaimdetailb.makedate =

                                          dd.makedate)

                              and d.riskcode = dd.riskcode

                              and dd.makedate <= date

                            '2015-01-01' - 1

                              and (dd.clmstate not in ('60', '70', '100') or

                                  dd.clmstate is null)

                              and (bb.endcasedate is null or

                                  bb.endcasedate > date '2015-01-01' - 1))),

                   0) pay2,

               splitstr_jzy(getSumPlanPrem(c.grpcontno,

                                           d.insuredno,

                                           date '2015-01-01',

                                           date '2015-11-30'),

                            1) prem,

               splitstr_jzy(getSumPlanPrem(c.grpcontno,

                                           d.insuredno,

                                           date '2015-01-01',

                                           date '2015-11-30'),

                            2) prem2,

               splitstr_jzy(getSumPlanPrem(c.grpcontno,

                                           d.insuredno,

                                           date '2015-01-01',

                                           date '2015-11-30'),

                            3) prem3

          from lccont c, lcinsured d, lcgrpcont g

         where c.grpcontno = d.grpcontno

           and c.contno = d.contno

           and c.insuredno = d.insuredno

           and c.grpcontno = g.grpcontno

           and g.appflag = '1'

           and ((g.cvalidate >= date '2015-01-01' and g.cvalidate <= date

                '2015-11-30') or

               (g.insurprotocoldate >= date

                '2015-01-01' and g.insurprotocoldate <= date '2015-11-30') or

               (g.cvalidate <= date

                '2015-01-01' and g.insurprotocoldate >= date '2015-11-30'))

           and c.managecom like '86%'

           and g.conttype = '2') rs

group by rs.managecom, rs.insurername, rs.contplancode;



以下为该SQL的执行计划和统计信息:

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------                               

| Id  | Operation                                | Name                           | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  |  OMem |  1Mem | Used-Mem |                               

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------                               

|   0 | SELECT STATEMENT                         |                                |      1 |        |     13 |00:03:41.72 |      21M|      6 |       |       |          |                               

|   1 |  TABLE ACCESS BY INDEX ROWID             | LDCODE                         |      2 |      1 |      2 |00:00:00.01 |       6 |      0 |       |       |          |                               

|*  2 |   INDEX UNIQUE SCAN                      | PK_LDCODE                      |      2 |      1 |      2 |00:00:00.01 |       4 |      0 |       |       |          |                               

|*  3 |  COUNT STOPKEY                           |                                |   4800 |        |   4800 |00:00:00.09 |    9600 |      0 |       |       |          |                               

|   4 |   TABLE ACCESS BY INDEX ROWID            | LCCONTPLAN                     |   4800 |      1 |   4800 |00:00:00.08 |    9600 |      0 |       |       |          |                               

|*  5 |    INDEX RANGE SCAN                      | PK_LCCONTPLAN                  |   4800 |      1 |   4800 |00:00:00.05 |    4800 |      0 |       |       |          |                               

|   6 |  SORT AGGREGATE                          |                                |  60166 |      1 |  60166 |00:00:02.22 |     140K|      1 |       |       |          |                               

|   7 |   NESTED LOOPS                           |                                |  60166 |      1 |  18122 |00:00:02.02 |     140K|      1 |       |       |          |                               

|*  8 |    TABLE ACCESS BY INDEX ROWID           | LLCLAIMDETAIL                  |  60166 |      1 |  24885 |00:00:01.44 |   90493 |      1 |       |       |          |                               

|*  9 |     INDEX RANGE SCAN                     | IDX_LLCLAIMDETAIL_CSTMNO       |  60166 |      2 |  35213 |00:00:00.83 |   60776 |      1 |       |       |          |                               

|* 10 |    TABLE ACCESS BY INDEX ROWID           | LLCLAIM                        |  24885 |      1 |  18122 |00:00:00.48 |   49777 |      0 |       |       |          |                               

|* 11 |     INDEX UNIQUE SCAN                    | PK_LLCLAIM                     |  24885 |      1 |  24885 |00:00:00.20 |   24889 |      0 |       |       |          |                               

|  12 |     SORT AGGREGATE                       |                                |  60166 |      1 |  60166 |00:00:09.15 |    1313K|      5 |       |       |          |                               

|* 13 |      FILTER                              |                                |  60166 |        |   2425 |00:00:09.01 |    1313K|      5 |       |       |          |                               

|  14 |       NESTED LOOPS                       |                                |  60166 |      3 |  16062 |00:00:06.71 |    1120K|      5 |       |       |          |                               

|* 15 |        TABLE ACCESS BY INDEX ROWID       | LLCLAIMPOLICYBAK               |  60166 |      3 |    460K|00:00:03.55 |     199K|      5 |       |       |          |                               

|* 16 |         INDEX RANGE SCAN                 | IDX_LLCLAIMPOLICYBAK_INSUREDNO |  60166 |    117 |    598K|00:00:01.36 |     122K|      1 |       |       |          |                               

|* 17 |        TABLE ACCESS BY INDEX ROWID       | LLCLAIM                        |    460K|      1 |  16062 |00:00:02.58 |     920K|      0 |       |       |          |                               

|* 18 |         INDEX UNIQUE SCAN                | PK_LLCLAIM                     |    460K|      1 |    460K|00:00:01.43 |     460K|      0 |       |       |          |                               

|  19 |       SORT AGGREGATE                     |                                |  12397 |      1 |  12397 |00:00:02.21 |     192K|      0 |       |       |          |                               

|  20 |        NESTED LOOPS ANTI                 |                                |  12397 |      1 |    104K|00:00:02.08 |     192K|      0 |       |       |          |                               

|  21 |         NESTED LOOPS                     |                                |  12397 |      1 |    143K|00:00:01.66 |     142K|      0 |       |       |          |                               

|* 22 |          TABLE ACCESS BY INDEX ROWID     | LLCLAIM                        |  12397 |      1 |  12397 |00:00:00.09 |   24796 |      0 |       |       |          |                               

|* 23 |           INDEX UNIQUE SCAN              | PK_LLCLAIM                     |  12397 |      1 |  12397 |00:00:00.05 |   12399 |      0 |       |       |          |                               

|* 24 |          TABLE ACCESS BY INDEX ROWID     | LLCLAIMPOLICYBAK               |  12397 |      1 |    143K|00:00:01.55 |     117K|      0 |       |       |          |                               

|* 25 |           INDEX RANGE SCAN               | IDX_LLCLAIMPOLICYBAK_CLMNO     |  12397 |      1 |   1110K|00:00:00.11 |   29945 |      0 |       |       |          |                               

|* 26 |         TABLE ACCESS BY INDEX ROWID      | LLCLAIMDETAILB                 |  16011 |      1 |   2839 |00:00:00.28 |   50149 |      0 |       |       |          |                               

|* 27 |          INDEX RANGE SCAN                | PK_LLCLAIMDETAILB              |  16011 |      2 |  50957 |00:00:00.15 |   32849 |      0 |       |       |          |                               

|  28 |          SORT AGGREGATE                  |                                |  60166 |      1 |  60166 |00:00:01.40 |     199K|      0 |       |       |          |                               

|* 29 |           FILTER                         |                                |  60166 |        |      0 |00:00:01.29 |     199K|      0 |       |       |          |                               

|  30 |            NESTED LOOPS                  |                                |  60166 |      1 |      0 |00:00:01.24 |     199K|      0 |       |       |          |                               

|* 31 |             TABLE ACCESS BY INDEX ROWID  | LLCLAIMPOLICYBAK               |  60166 |      1 |      0 |00:00:01.19 |     199K|      0 |       |       |          |                               

|* 32 |              INDEX RANGE SCAN            | IDX_LLCLAIMPOLICYBAK_INSUREDNO |  60166 |    117 |    598K|00:00:00.39 |     122K|      0 |       |       |          |                               

|* 33 |             TABLE ACCESS BY INDEX ROWID  | LLCLAIM                        |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |                               

|* 34 |              INDEX UNIQUE SCAN           | PK_LLCLAIM                     |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |                               

|  35 |            SORT AGGREGATE                |                                |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |                               

|  36 |             NESTED LOOPS ANTI            |                                |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |                               

|  37 |              NESTED LOOPS                |                                |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |                               

|* 38 |               TABLE ACCESS BY INDEX ROWID| LLCLAIM                        |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |                               

|* 39 |                INDEX UNIQUE SCAN         | PK_LLCLAIM                     |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |                               

|* 40 |               TABLE ACCESS BY INDEX ROWID| LLCLAIMPOLICYBAK               |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |                               

|* 41 |                INDEX RANGE SCAN          | IDX_LLCLAIMPOLICYBAK_CLMNO     |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |                               

|* 42 |              TABLE ACCESS BY INDEX ROWID | LLCLAIMDETAILB                 |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |       |       |          |                               

|* 43 |               INDEX RANGE SCAN           | PK_LLCLAIMDETAILB              |      0 |      2 |      0 |00:00:00.01 |       0 |      0 |       |       |          |                               

|  44 |  HASH GROUP BY                           |                                |      1 |      5 |     13 |00:03:41.72 |      21M|      6 |   728K|   728K|  989K (0)|                               

|  45 |   VIEW                                   |                                |      1 |   2233 |  60166 |00:03:41.17 |      21M|      6 |       |       |          |                               

|* 46 |    HASH JOIN                             |                                |      1 |   2233 |  60166 |00:00:03.30 |    7461 |      0 |   820K|   820K| 1183K (0)|                               

|* 47 |     TABLE ACCESS FULL                    | LCGRPCONT                      |      1 |     48 |     48 |00:00:00.01 |      16 |      0 |       |       |          |                               

|* 48 |     HASH JOIN                            |                                |      1 |   2256 |  65410 |00:00:03.14 |    7445 |      0 |  5933K|  2261K| 6542K (0)|                               

|* 49 |      TABLE ACCESS FULL                   | LCCONT                         |      1 |  49349 |  65410 |00:00:02.71 |    4163 |      0 |       |       |          |                               

|  50 |      TABLE ACCESS FULL                   | LCINSURED                      |      1 |  65400 |  65410 |00:00:00.07 |    3282 |      0 |       |       |          |                               

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------                               

                                                                                                                                                                                                        

Predicate Information (identified by operation id):                                                                                                                                                     

---------------------------------------------------                                                                                                                                                     

                                                                                                                                                                                                        

   2 - access("CODETYPE"='station' AND "CODE"=:B1)                                                                                                                                                      

   3 - filter(ROWNUM=1)                                                                                                                                                                                 

   5 - access("LN"."GRPCONTNO"=:B1 AND "LN"."CONTPLANCODE"=:B2)                                                                                                                                         

   8 - filter(("A"."GRPCONTNO"=:B1 AND "A"."GIVETYPE"<>'1'))                                                                                                                                            

   9 - access("A"."CUSTOMERNO"=:B1)                                                                                                                                                                     

  10 - filter(("B"."ENDCASEDATE">=TO_DATE(' 2015-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND "B"."CLMSTATE"='60' AND "B"."ENDCASEDATE"<=TO_DATE(' 2015-11-30                                         

              00:00:00', 'syyyy-mm-dd hh24:mi:ss')))                                                                                                                                                    

  11 - access("A"."CLMNO"="B"."CLMNO")                                                                                                                                                                  

  13 - filter("D"."SEQNO"=)                                                                                                                                                                             

  15 - filter(("D"."GRPCONTNO"=:B1 AND "D"."MAKEDATE"<=TO_DATE(' 2015-11-30 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND ("D"."CLMSTATE" IS NULL OR                                                         

              ("D"."CLMSTATE"<>'60' AND "D"."CLMSTATE"<>'70' AND "D"."CLMSTATE"<>'100'))))                                                                                                              

  16 - access("D"."INSUREDNO"=:B1)                                                                                                                                                                      

  17 - filter(("B"."ENDCASEDATE" IS NULL OR "B"."ENDCASEDATE">TO_DATE(' 2015-11-30 00:00:00', 'syyyy-mm-dd hh24:mi:ss')))                                                                               

  18 - access("D"."CLMNO"="B"."CLMNO")                                                                                                                                                                  

  22 - filter(("BB"."ENDCASEDATE" IS NULL OR "BB"."ENDCASEDATE">TO_DATE(' 2015-11-30 00:00:00', 'syyyy-mm-dd hh24:mi:ss')))                                                                             

  23 - access("BB"."CLMNO"=:B1)                                                                                                                                                                         

  24 - filter(("DD"."GRPCONTNO"=:B1 AND "DD"."RISKCODE"=:B2 AND "DD"."MAKEDATE"<=TO_DATE(' 2015-11-30 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND ("DD"."CLMSTATE"                                         

              IS NULL OR ("DD"."CLMSTATE"<>'60' AND "DD"."CLMSTATE"<>'70' AND "DD"."CLMSTATE"<>'100'))))                                                                                                

  25 - access("DD"."CLMNO"="BB"."CLMNO")                                                                                                                                                                

       filter("DD"."CLMNO"=:B1)                                                                                                                                                                         

  26 - filter(("LLCLAIMDETAILB"."RISKCODE"=:B1 AND "LLCLAIMDETAILB"."MAKEDATE"<=TO_DATE(' 2015-11-30 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND                                                           

              "LLCLAIMDETAILB"."RISKCODE"="DD"."RISKCODE" AND "LLCLAIMDETAILB"."MAKEDATE"="DD"."MAKEDATE"))                                                                                             

  27 - access("LLCLAIMDETAILB"."CLMNO"=:B1)                                                                                                                                                             

       filter("LLCLAIMDETAILB"."CLMNO"="DD"."CLMNO")                                                                                                                                                    

  29 - filter("D"."SEQNO"=)                                                                                                                                                                             

  31 - filter(("D"."MAKEDATE"<=TO_DATE(' 2014-12-31 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND "D"."GRPCONTNO"=:B1 AND ("D"."CLMSTATE" IS NULL OR                                                         

              ("D"."CLMSTATE"<>'60' AND "D"."CLMSTATE"<>'70' AND "D"."CLMSTATE"<>'100'))))                                                                                                              

  32 - access("D"."INSUREDNO"=:B1)                                                                                                                                                                      

  33 - filter(("B"."ENDCASEDATE">TO_DATE(' 2014-12-31 00:00:00', 'syyyy-mm-dd hh24:mi:ss') OR "B"."ENDCASEDATE" IS NULL))                                                                               

  34 - access("D"."CLMNO"="B"."CLMNO")                                                                                                                                                                  

  38 - filter(("BB"."ENDCASEDATE">TO_DATE(' 2014-12-31 00:00:00', 'syyyy-mm-dd hh24:mi:ss') OR "BB"."ENDCASEDATE" IS NULL))                                                                             

  39 - access("BB"."CLMNO"=:B1)                                                                                                                                                                         

  40 - filter(("DD"."MAKEDATE"<=TO_DATE(' 2014-12-31 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND "DD"."GRPCONTNO"=:B1 AND "DD"."RISKCODE"=:B2 AND ("DD"."CLMSTATE"                                         

              IS NULL OR ("DD"."CLMSTATE"<>'60' AND "DD"."CLMSTATE"<>'70' AND "DD"."CLMSTATE"<>'100'))))                                                                                                

  41 - access("DD"."CLMNO"="BB"."CLMNO")                                                                                                                                                                

       filter("DD"."CLMNO"=:B1)                                                                                                                                                                         

  42 - filter(("LLCLAIMDETAILB"."RISKCODE"=:B1 AND "LLCLAIMDETAILB"."MAKEDATE"<=TO_DATE(' 2014-12-31 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND                                                           

              "LLCLAIMDETAILB"."RISKCODE"="DD"."RISKCODE" AND "LLCLAIMDETAILB"."MAKEDATE"="DD"."MAKEDATE"))                                                                                             

  43 - access("LLCLAIMDETAILB"."CLMNO"=:B1)                                                                                                                                                             

       filter("LLCLAIMDETAILB"."CLMNO"="DD"."CLMNO")                                                                                                                                                    

  46 - access("C"."GRPCONTNO"="G"."GRPCONTNO")                                                                                                                                                          

  47 - filter(("G"."CONTTYPE"='2' AND "G"."APPFLAG"='1' AND (("G"."INSURPROTOCOLDATE">=TO_DATE(' 2015-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss') AND                                                    

              "G"."INSURPROTOCOLDATE"<=TO_DATE(' 2015-11-30 00:00:00', 'syyyy-mm-dd hh24:mi:ss')) OR ("G"."CVALIDATE">=TO_DATE(' 2015-01-01 00:00:00', 'syyyy-mm-dd                                     

              hh24:mi:ss') AND "G"."CVALIDATE"<=TO_DATE(' 2015-11-30 00:00:00', 'syyyy-mm-dd hh24:mi:ss')) OR ("G"."INSURPROTOCOLDATE">=TO_DATE(' 2015-11-30 00:00:00',                                 

              'syyyy-mm-dd hh24:mi:ss') AND "G"."CVALIDATE"<=TO_DATE(' 2015-01-01 00:00:00', 'syyyy-mm-dd hh24:mi:ss')))))                                                                              

  48 - access("C"."GRPCONTNO"="D"."GRPCONTNO" AND "C"."CONTNO"="D"."CONTNO" AND "C"."INSUREDNO"="D"."INSUREDNO")                                                                                        

  49 - filter("C"."MANAGECOM" LIKE '86%')                                                                                                                                                               





Statistics

----------------------------------------------------------

    1944733  recursive calls

    4331962  db block gets

   17399665  consistent gets

          2  physical reads

        116  redo size

       2735  bytes sent via SQL*Net to client

       5649  bytes received via SQL*Net from client

          2  SQL*Net roundtrips to/from client

     180506  sorts (memory)

          0  sorts (disk)

         13  rows processed

         

二,处理过程。

    从统计信息上看,为了完成最终只有13行(13  rows processed)的一个查询,逻辑读17399665个块次(17399665  consistent gets),以数据库块8K大小的默认值计算的话,意味着前后一共处理了130G左右数据。递归调用也很高(1944733  recursive calls)。

    递归调用也是一种比较消耗资源的操作,当发生空间不足,需要更多的分区(extent)时;引起触发器工作时;执行存储过程、自定义函数等时;需要从磁盘读取数据字典信息时等情况下,都会引起递归调用。也可以简单理解为需要执行本SQL之外的其它SQL时,就称之为递归调用。

    再从执行计划上看,注意到starts列上有不少成千上万的值,甚至还有460K的。而该列是表示相应对象被访问的次数的。如果相应的对象的访问方法还是全表扫描或是索引全扫描,那就更需要引起注意。

    所以,初步看上去,调优的潜力很大。(返回行少,但资源消耗大的,通常调优的潜力都比较大。)

    在阅读SQL代码时,发现其中有这样的代码:

    splitstr_jzy(getSumPlanPrem(c.grpcontno,

                                           d.insuredno,

                                           date '2015-01-01',

                                           date '2015-11-30')

    显然,这里的splitstr_jzy和getSumPlanPrem都是自定义函数。考虑到前边较高的递归调用,所以,很可能这种过高的递归调用是因这两个自定义函数所产生的。

    这时,第一个想法产生了,即暂时注释掉这些函数的引用,看看情况会如何。如果性能有明显提升,说明问题的根源肯定与这些自定义函数相关。我们就要把主要精力先放在这些自定义函数上。如果性能提升不大,那么说明问题的根源与这些自定义函数关系不大,我们需要继续分析其它方面的问题。

    为节省往篇幅,这里只提供与这些自定义函数相关的代码:

               /*splitstr_jzy(getSumPlanPrem(c.grpcontno,

                                           d.insuredno,

                                           date '2015-01-01',

                                           date '2015-11-30'),

                            1)*/ 0 prem,

               /*splitstr_jzy(getSumPlanPrem(c.grpcontno,

                                           d.insuredno,

                                           date '2015-01-01',

                                           date '2015-11-30'),

                            2)*/ 0 prem2,

               /*splitstr_jzy(getSumPlanPrem(c.grpcontno,

                                           d.insuredno,

                                           date '2015-01-01',

                                           date '2015-11-30'),

                            3)*/ 0 prem3

          from lccont c, lcinsured d, lcgrpcont g

     从以上代码可见,通过注释掉相关函数,并固定设置一个0值来代替原函数执行的结果。之所以用一个固定值来代替,是因为相关自定义函数的调用处于子查询中,外部查询需要使用这些函数的结果,如果完全注释掉,还要修改外部查询的相关代码,动静比较大。

   再次执行注释掉相关函数的SQL,统计信息的结果如下:

     Statistics

----------------------------------------------------------

       2718  recursive calls

          0  db block gets

    1671681  consistent gets

       3255  physical reads

        260  redo size

       2432  bytes sent via SQL*Net to client

       5762  bytes received via SQL*Net from client

          2  SQL*Net roundtrips to/from client

         99  sorts (memory)

          0  sorts (disk)

         13  rows processed

         

     很明显,递归调用减少为约2700次,逻辑读1671681个块次,相较于注释前,分别只是原来的千分之一和十分之一。而且实际的执行时间也只要不到20秒了。看来这两个函数是我们主攻的方向。

     通过对getSumPlanPrem源代码的分析,发现其主要是对若干表的单表查询,并将获得的指定列的值赋给变量,然后根据对这些变量的比较和计算,最终形成三个数值结果,但返回时,是将三个数值结果转换为字符,并以逗号分隔拼接后,以一个字符串的形式返回。而这些单表的查询中,有相当一部分的表上并没有适用的索引(不是没有索引,而是没有适合相关查询的索引。),而使得部分查询需要访问更多的数据块才能得到需要的结果,需要消耗更多的资源。比如:

     select c.prem

          into tPrem

          from lccont c

         where c.grpcontno = cGrpContNo

           and c.insuredno = cInsur edNo;

    这样一个查询。表lccont有一个名为PK_LCCONT的主键,主键列为(CONTNO)。grpcontno列和insuredno列各有一个单列索引,而该表共有约6万行数据。仅从这条语句的角度来看,无论是走这两列中的任一个索引,最终都要回表去获取PREM列的值,不如将grpcontno列、insuredno列和prem列编入一个复合索引中,这样不仅不用回表,而且这个索引的选择性会更好。这里简单展开一下,对于复合索引,创建时列的顺序是很有讲究的。要把WHERE条件中出现的列写到前边,如果有多个,哪个重复值高,就把哪一列写到前面(通常而言)。这样,当某个SQL中只有对第二列条件的过滤时,就有可能用上该复合索引(以索引跳跃扫描的方式来访问),反之,把重复值低的放到前面,当只有对第二列条件的过滤时,这个复合索引很可能是用不上的。而造成这种状况的原因是和复合索引的存储结构相关的。

    再次运行原SQL(未注释自定义函数代码的SQL),统计信息如下:

    Statistics

----------------------------------------------------------

    1944042  recursive calls

    4331962  db block gets

    4623587  consistent gets

        351  physical reads

        144  redo size

       2735  bytes sent via SQL*Net to client

       5650  bytes received via SQL*Net from client

          2  SQL*Net roundtrips to/from client

     180498  sorts (memory)

          0  sorts (disk)

         13  rows processed

    逻辑读降为4623587个块次(4623587  consistent gets),只是原来的四分之一了。但由于调用自定义函数的次数没有减少,所以,递归调用的次数没有改变。

    再来看这段对自定义函数调用的代码:

     splitstr_jzy(getSumPlanPrem(c.grpcontno,

                                 d.insuredno,

                                 date '2015-01-01',

                                 date '2015-11-30'),

                            1) prem,

    splitstr_jzy(getSumPlanPrem(c.grpcontno,

                                d.insuredno,

                                date '2015-01-01',

                                date '2015-11-30'),

                            2)0 prem2,

    splitstr_jzy(getSumPlanPrem(c.grpcontno,

                                d.insuredno,

                                date '2015-01-01',

                                date '2015-11-30'),

                            3) prem3

      可以看到对getSumPlanPrem的调用重复了三次,且输入的参数完全一样。而splitstr_jzy函数的作用就是把以逗号分隔的,包括三个结果值的字符串根据第二个输入参数的值,把第N段的结果拆分出来。

      如果我们这里把它变成只调用一次,那么该自定义函数的调用就会减少三分之二。那么无论是递归调用的次数,还是函数运行产生的资源消耗都会减少。

      所以,我们这里只保留一次调用,而这个splitstr_jzy自定义函数的功能,完全可以用正则表达式来实现,即把外部查询对原prem,prem2和prem3的引用,分别改为regexp_substr(prem_all,'[^,]+',1,1),  regexp_substr(prem_all,'[^,]+',1,2), regexp_substr(prem_all,'[^,]+',1,3)。

      再次修改后的代码大致为:

      select ...

                regexp_substr(prem_all,'[^,]+',1,1) prem,

                regexp_substr(prem_all,'[^,]+',1,2) prem2,

                regexp_substr(prem_all,'[^,]+',1,3) prem3,

             ...

      from (

             select ....

                    getSumPlanPrem(c.grpcontno,

                                   d.insuredno,

                                   date '2015-01-01',

                                   date '2015-11-30') prem_all

             from .....

             where .....)

      where ....

      group by .....;

      

      对这样处理的SQL代码再次运行,其统计信息如下:

      Statistics

----------------------------------------------------------

     527686  recursive calls

          0  db block gets

    2416931  consistent gets

         93  physical reads

        188  redo size

       2917  bytes sent via SQL*Net to client

       6671  bytes received via SQL*Net from client

          2  SQL*Net roundtrips to/from client

      60166  sorts (memory)

          0  sorts (disk)

         13  rows processed

      递归调用的次数变为527686次,逻辑读的次数为2416931次,相较于上一次改写,又分别下降了约70%和50%。实际的执行时间也减少到约30秒。

      而此时的执行计划是:

      ----------------------------------------------------------------------------------------------------------------------------------------------------------------

| Id  | Operation                                | Name                           | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |

----------------------------------------------------------------------------------------------------------------------------------------------------------------

|   0 | SELECT STATEMENT                         |                                |      1 |        |     13 |00:00:32.02 |    2416K|       |       |          |

|   1 |  TABLE ACCESS BY INDEX ROWID             | LDCODE                         |      2 |      1 |      2 |00:00:00.01 |       6 |       |       |          |

|*  2 |   INDEX UNIQUE SCAN                      | PK_LDCODE                      |      2 |      1 |      2 |00:00:00.01 |       4 |       |       |          |

|*  3 |  COUNT STOPKEY                           |                                |   4800 |        |   4800 |00:00:00.07 |    9600 |       |       |          |

|   4 |   TABLE ACCESS BY INDEX ROWID            | LCCONTPLAN                     |   4800 |      1 |   4800 |00:00:00.05 |    9600 |       |       |          |

|*  5 |    INDEX RANGE SCAN                      | PK_LCCONTPLAN                  |   4800 |      1 |   4800 |00:00:00.03 |    4800 |       |       |          |

|   6 |  SORT AGGREGATE                          |                                |  60166 |      1 |  60166 |00:00:01.30 |     140K|       |       |          |

|   7 |   NESTED LOOPS                           |                                |  60166 |      1 |  18122 |00:00:01.17 |     140K|       |       |          |

|*  8 |    TABLE ACCESS BY INDEX ROWID           | LLCLAIMDETAIL                  |  60166 |      1 |  24808 |00:00:00.82 |   90718 |       |       |          |

|*  9 |     INDEX RANGE SCAN                     | IDX_LLCLAIMDETAIL_CSTMNO       |  60166 |      2 |  35102 |00:00:00.50 |   60780 |       |       |          |

|* 10 |    TABLE ACCESS BY INDEX ROWID           | LLCLAIM                        |  24808 |      1 |  18122 |00:00:00.28 |   49622 |       |       |          |

|* 11 |     INDEX UNIQUE SCAN                    | PK_LLCLAIM                     |  24808 |      1 |  24808 |00:00:00.14 |   24810 |       |       |          |

|  12 |     SORT AGGREGATE                       |                                |  60166 |      1 |  60166 |00:00:07.19 |    1315K|       |       |          |

|* 13 |      FILTER                              |                                |  60166 |        |   2421 |00:00:07.10 |    1315K|       |       |          |

|  14 |       NESTED LOOPS                       |                                |  60166 |      3 |  16062 |00:00:04.91 |    1121K|       |       |          |

|* 15 |        TABLE ACCESS BY INDEX ROWID       | LLCLAIMPOLICYBAK               |  60166 |      3 |    460K|00:00:01.90 |     201K|       |       |          |

|* 16 |         INDEX RANGE SCAN                 | IDX_LLCLAIMPOLICYBAK_INSUREDNO |  60166 |    117 |    610K|00:00:00.57 |     122K|       |       |          |

|* 17 |        TABLE ACCESS BY INDEX ROWID       | LLCLAIM                        |    460K|      1 |  16062 |00:00:02.63 |     920K|       |       |          |

|* 18 |         INDEX UNIQUE SCAN                | PK_LLCLAIM                     |    460K|      1 |    460K|00:00:01.48 |     460K|       |       |          |

|  19 |       SORT AGGREGATE                     |                                |  12397 |      1 |  12397 |00:00:02.10 |     193K|       |       |          |

|  20 |        NESTED LOOPS ANTI                 |                                |  12397 |      1 |    104K|00:00:01.98 |     193K|       |       |          |

|  21 |         NESTED LOOPS                     |                                |  12397 |      1 |    143K|00:00:01.61 |     143K|       |       |          |

|* 22 |          TABLE ACCESS BY INDEX ROWID     | LLCLAIM                        |  12397 |      1 |  12397 |00:00:00.09 |   24796 |       |       |          |

|* 23 |           INDEX UNIQUE SCAN              | PK_LLCLAIM                     |  12397 |      1 |  12397 |00:00:00.05 |   12399 |       |       |          |

|* 24 |          TABLE ACCESS BY INDEX ROWID     | LLCLAIMPOLICYBAK               |  12397 |      1 |    143K|00:00:01.50 |     118K|       |       |          |

|* 25 |           INDEX RANGE SCAN               | IDX_LLCLAIMPOLICYBAK_CLMNO     |  12397 |      1 |   1116K|00:00:00.10 |   29984 |       |       |          |

|* 26 |         TABLE ACCESS BY INDEX ROWID      | LLCLAIMDETAILB                 |  16011 |      1 |   2849 |00:00:00.24 |   50276 |       |       |          |

|* 27 |          INDEX RANGE SCAN                | PK_LLCLAIMDETAILB              |  16011 |      2 |  51475 |00:00:00.13 |   32852 |       |       |          |

|  28 |          SORT AGGREGATE                  |                                |  60166 |      1 |  60166 |00:00:01.29 |     201K|       |       |          |

|* 29 |           FILTER                         |                                |  60166 |        |      0 |00:00:01.20 |     201K|       |       |          |

|  30 |            NESTED LOOPS                  |                                |  60166 |      1 |      0 |00:00:01.16 |     201K|       |       |          |

|* 31 |             TABLE ACCESS BY INDEX ROWID  | LLCLAIMPOLICYBAK               |  60166 |      1 |      0 |00:00:01.11 |     201K|       |       |          |

|* 32 |              INDEX RANGE SCAN            | IDX_LLCLAIMPOLICYBAK_INSUREDNO |  60166 |    117 |    610K|00:00:00.35 |     122K|       |       |          |

|* 33 |             TABLE ACCESS BY INDEX ROWID  | LLCLAIM                        |      0 |      1 |      0 |00:00:00.01 |       0 |       |       |          |

|* 34 |              INDEX UNIQUE SCAN           | PK_LLCLAIM                     |      0 |      1 |      0 |00:00:00.01 |       0 |       |       |          |

|  35 |            SORT AGGREGATE                |                                |      0 |      1 |      0 |00:00:00.01 |       0 |       |       |          |

|  36 |             NESTED LOOPS ANTI            |                                |      0 |      1 |      0 |00:00:00.01 |       0 |       |       |          |

|  37 |              NESTED LOOPS                |                                |      0 |      1 |      0 |00:00:00.01 |       0 |       |       |          |

|* 38 |               TABLE ACCESS BY INDEX ROWID| LLCLAIM                        |      0 |      1 |      0 |00:00:00.01 |       0 |       |       |          |

|* 39 |                INDEX UNIQUE SCAN         | PK_LLCLAIM                     |      0 |      1 |      0 |00:00:00.01 |       0 |       |       |          |

|* 40 |               TABLE ACCESS BY INDEX ROWID| LLCLAIMPOLICYBAK               |      0 |      1 |      0 |00:00:00.01 |       0 |       |       |          |

|* 41 |                INDEX RANGE SCAN          | IDX_LLCLAIMPOLICYBAK_CLMNO     |      0 |      1 |      0 |00:00:00.01 |       0 |       |       |          |

|* 42 |              TABLE ACCESS BY INDEX ROWID | LLCLAIMDETAILB                 |      0 |      1 |      0 |00:00:00.01 |       0 |       |       |          |

|* 43 |               INDEX RANGE SCAN           | PK_LLCLAIMDETAILB              |      0 |      2 |      0 |00:00:00.01 |       0 |       |       |          |

|  44 |  HASH GROUP BY                           |                                |      1 |      5 |     13 |00:00:32.02 |    2416K|   728K|   728K|  994K (0)|

|  45 |   VIEW                                   |                                |      1 |   2233 |  60166 |00:00:30.08 |    2416K|       |       |          |

|* 46 |    HASH JOIN                             |                                |      1 |   2233 |  60166 |00:00:03.12 |    7461 |   820K|   820K| 1192K (0)|

|* 47 |     TABLE ACCESS FULL                    | LCGRPCONT                      |      1 |     48 |     48 |00:00:00.01 |      16 |       |       |          |

|* 48 |     HASH JOIN                            |                                |      1 |   2256 |  65410 |00:00:03.08 |    7445 |  5933K|  2261K| 6473K (0)|

|* 49 |      TABLE ACCESS FULL                   | LCCONT                         |      1 |  49349 |  65410 |00:00:02.78 |    4163 |       |       |          |

|  50 |      TABLE ACCESS FULL                   | LCINSURED                      |      1 |  65400 |  65410 |00:00:00.07 |    3282 |       |       |          |

----------------------------------------------------------------------------------------------------------------------------------------------------------------

    而下一步的重点,则要是要消除这些成千上万次的表连接次数。而结合谓词信息的分析,发现这里大部分的成千上万次表连接的原因,就是使用了大量的标量子查询(即只返回一行一列的子查询)。而这些子查询又都出现在外部查询的SELECT部分,这也就意味着,外部查询返回多少行,这个子查询就要执行多少次,相关的表就要被访问多少次。所以,进一步优化的方法,就是改写SQL,把这些子查询移到FROM 后面,这样相关表只要访问一次就可以。

    由于改写过程中出现改写后的结果与原SQL不一致,而用户又不愿提供原始数据供分析,以及担心SQL改动过大而可能引起的不利因素等原因,所以,进一步的优化没能进行下去。

   按我本意,是想在将大部分的超高表连接次数消除后,继续检查相关表上统计信息的准确性(从执行计划中可见,有部分处理步骤中,估算的行数与实际的行数相差较大。),以确保CBO不致因统计信息的问题而产生错误的执行计划。如果一切顺利,这个SQL的执行时间调到秒出是有可能的。

   

三、总结

    通过这个案例,有以下几点经验:

    1、当出现超高的递归调用时,要检查SQL中是否有自定义函数的调用,并尝试减少这些函数的调用,以及优化函数自身。

    2、当有内置函数可以实现的功能时,尽量使用系统内置函数。

    3、当逻辑读较高,且伴随有大量的表连接次数(starts列)时,可以通过改写SQL,达到减少表连接次数,降低逻辑读的目的。

    

   


你可能感兴趣的:(oracle,优化,自定义函数)