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


一、问题。
    下面这条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,达到减少表连接次数,降低逻辑读的目的。
    
   
   

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/22207394/viewspace-1869816/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/22207394/viewspace-1869816/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值